Complete.Org: Mailing Lists: Archives: freeciv-dev: October 2005:
[Freeciv-Dev] (PR#14365) battlegroups...
Home

[Freeciv-Dev] (PR#14365) battlegroups...

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
Subject: [Freeciv-Dev] (PR#14365) battlegroups...
From: "Jason Short" <jdorje@xxxxxxxxxxxxxxxxxxxxx>
Date: Mon, 17 Oct 2005 23:39:12 -0700
Reply-to: bugs@xxxxxxxxxxx

<URL: http://bugs.freeciv.org/Ticket/Display.html?id=14365 >

As a player, I define battlegroups as:

1.  The ability to select multiple units at once.
2.  The ability to give orders to all these selected units.
3.  The ability to store and recall these groups via hotkeys.

The warclient had battlegroups...of a sort.  Personally I could never 
figure out either the user interface or the code.  However I do believe 
it was rather lacking in that giving orders to groups required a 
separate interface than giving orders to a single unit.  In my ideal 
implementation, the behavior will be identical whether it is 1 or 100 
units selected.

Attached is a first implementation that does all three.
* The focus unit is changed to be a unit list.
* Goto/orders can operate on multiple units simultaneously.
* You may add units to the selection via shift-leftclick.
* Battlegroup IDs are assigned through keypresses (ctrl-1).
* Battlegroups are selected through keypresses (1).
* Units' battlegroups are sent to the server and saved.
* Each unit may only be in one battlegroup.
* Battlegroup IDs are drawn as part of the unit.  The city size graphics 
are used as fallback (which is pretty poor in amplio).

There are several behavior issues.
* The old shift-left-click behavior is replaced.  In general I think the 
quickselect needs to be replaced with a click-type enumeration so 
do_map_click can handle *all* clicks in a simple switch.
* There is no way to drag-and-select units (the selection rectangle 
selects only cities!).
* Shift-leftclick on a tile with multiple units still pops up the unit 
selection dialog...and selecting a unit here does not follow the "shift" 
(add) behavior.
* Shift-NUMBER doesn't add the battlegroup to the current selection. 
(This would be easy to add; I just thought of it now.)
* Documentation is needed.
* Sometimes battlegroups seem to get corrupted on disconnect+reconnect.
* Battlegroup commands should probably be in menus rather than "hidden" 
keyboard commands.

And several implementation issues.
* The hover units, focus units, and units passed to 
update_unit_info_label, request_unit_nuke, request_unit_paradrop, etc. 
must always be the same.  This is an issue with the current code; even 
though these functions allow you to pass in different units if you do so 
you'll only get buggy behavior.
* Assigning and selection of battlegroups is unnecessarily inefficient 
(not a problem in normal-sized games).
* More comments and general review needed.

Nonetheless it is quite playable.

-jason

Index: utility/genlist.c
===================================================================
--- utility/genlist.c   (revision 11150)
+++ utility/genlist.c   (working copy)
@@ -227,7 +227,24 @@
   return plink;
 }
 
+/************************************************************************
+  Return TRUE iff this element is in the list.
 
+  This is an O(n) operation.  Hence, "search".
+************************************************************************/
+bool genlist_search(struct genlist *pgenlist, const void *data)
+{
+  struct genlist_link *plink;
+
+  for (plink = pgenlist->head_link; plink; plink = plink->next) {
+    if (plink->dataptr == data) {
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
 /************************************************************************
  Sort the elements of a genlist.
  
Index: utility/genlist.h
===================================================================
--- utility/genlist.h   (revision 11150)
+++ utility/genlist.h   (working copy)
@@ -49,6 +49,8 @@
   See also the speclist module.
 ***********************************************************************/
 
+#include "support.h" /* bool */
+
 /* A single element of a genlist, storing the pointer to user
    data, and pointers to the next and previous elements:
 */
@@ -77,6 +79,8 @@
 void genlist_prepend(struct genlist *pgenlist, void *data);
 void genlist_unlink(struct genlist *pgenlist, void *punlink);
 
+bool genlist_search(struct genlist *pgenlist, const void *data);
+
 void genlist_sort(struct genlist *pgenlist,
                  int (*compar)(const void *, const void *));
 
Index: utility/support.h
===================================================================
--- utility/support.h   (revision 11150)
+++ utility/support.h   (working copy)
@@ -27,8 +27,49 @@
 #include <sys/types.h>
 #endif
 
-#include "shared.h"            /* bool type and fc__attribute */
+#ifdef TRUE
+#undef TRUE
+#endif
 
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#define TRUE true
+#define FALSE false
+
+#if __BEOS__
+#include <posix/be_prim.h>
+#define __bool_true_false_are_defined 1
+#else
+#ifdef HAVE_STDBOOL_H
+#include <stdbool.h>
+#else /* Implement <stdbool.h> ourselves */
+#undef bool
+#undef true
+#undef false
+#undef __bool_true_false_are_defined
+#define bool fc_bool
+#define true  1
+#define false 0
+#define __bool_true_false_are_defined 1
+typedef unsigned int fc_bool;
+#endif /* ! HAVE_STDBOOL_H */
+#endif /* ! __BEOS__ */
+
+/* Want to use GCC's __attribute__ keyword to check variadic
+ * parameters to printf-like functions, without upsetting other
+ * compilers: put any required defines magic here.
+ * If other compilers have something equivalent, could also
+ * work that out here.   Should this use configure stuff somehow?
+ * --dwp
+ */
+#if defined(__GNUC__)
+#define fc__attribute(x)  __attribute__(x)
+#else
+#define fc__attribute(x)
+#endif
+
 int mystrcasecmp(const char *str0, const char *str1);
 int mystrncasecmp(const char *str0, const char *str1, size_t n);
 
Index: utility/speclist.h
===================================================================
--- utility/speclist.h  (revision 11150)
+++ utility/speclist.h  (working copy)
@@ -37,6 +37,7 @@
       void foo_list_append(struct foo_list *This, foo_t *pfoo);
       void foo_list_unlink(struct foo_list *This, foo_t *pfoo);
       void foo_list_unlink_all(struct foo_list *This);
+      bool foo_list_search(struct foo_list *this, foo_t *pfoo);
       void foo_list_sort(struct foo_list *This, 
          int (*compar)(const void *, const void *));
 
@@ -121,6 +122,11 @@
   free(tthis);
 }
 
+static inline bool SPECLIST_FOO(_list_search) (SPECLIST_LIST *tthis, const 
SPECLIST_TYPE *pfoo)
+{
+  return genlist_search(tthis->list, pfoo);
+}
+
 static inline void SPECLIST_FOO(_list_sort) (SPECLIST_LIST * tthis, int 
(*compar) (const void *, const void *))
 {
   genlist_sort(tthis->list, compar);
Index: utility/shared.h
===================================================================
--- utility/shared.h    (revision 11150)
+++ utility/shared.h    (working copy)
@@ -17,44 +17,14 @@
 #include <string.h>            /* memset */
 #include <time.h>              /* time_t */
 
+#include "support.h" /* bool, fc__attribute */
+
 #ifdef HAVE_CONFIG_H
 #ifndef FC_CONFIG_H            /* this should be defined in config.h */
 #error Files including fcintl.h should also include config.h directly
 #endif
 #endif
 
-#if __BEOS__
-#include <posix/be_prim.h>
-#define __bool_true_false_are_defined 1
-#else
-#ifdef HAVE_STDBOOL_H
-#include <stdbool.h>
-#else /* Implement <stdbool.h> ourselves */
-#undef bool
-#undef true
-#undef false
-#undef __bool_true_false_are_defined
-#define bool fc_bool
-#define true  1
-#define false 0
-#define __bool_true_false_are_defined 1
-typedef unsigned int fc_bool;
-#endif /* ! HAVE_STDBOOL_H */
-#endif /* ! __BEOS__ */
-
-/* Want to use GCC's __attribute__ keyword to check variadic
- * parameters to printf-like functions, without upsetting other
- * compilers: put any required defines magic here.
- * If other compilers have something equivalent, could also
- * work that out here.   Should this use configure stuff somehow?
- * --dwp
- */
-#if defined(__GNUC__)
-#define fc__attribute(x)  __attribute__(x)
-#else
-#define fc__attribute(x)
-#endif
-
 #define MAX_LEN_ADDR     256   /* see also MAXHOSTNAMELEN and RFC 1123 2.1 */
 #define MAX_LEN_PATH 4095
 
@@ -62,17 +32,6 @@
    another unreachable condition. */
 #define FC_INFINITY            (1000 * 1000 * 1000)
 
-#ifdef TRUE
-#undef TRUE
-#endif
-
-#ifdef FALSE
-#undef FALSE
-#endif
-
-#define TRUE true
-#define FALSE false
-
 #ifndef MAX
 #define MAX(x,y) (((x)>(y))?(x):(y))
 #define MIN(x,y) (((x)<(y))?(x):(y))
Index: server/unittools.c
===================================================================
--- server/unittools.c  (revision 11150)
+++ server/unittools.c  (working copy)
@@ -1766,6 +1766,7 @@
     packet->transported_by = punit->transported_by;
   }
   packet->occupy = get_transporter_occupancy(punit);
+  packet->battlegroup = punit->battlegroup;
   packet->has_orders = punit->has_orders;
   if (punit->has_orders) {
     int i;
Index: server/unithand.c
===================================================================
--- server/unithand.c   (revision 11150)
+++ server/unithand.c   (working copy)
@@ -1418,6 +1418,22 @@
 }
 
 /**************************************************************************
+  Assign the unit to the battlegroup.
+
+  Battlegroups are handled entirely by the client, so all we have to
+  do here is save the battlegroup ID so that it'll be persistent.
+**************************************************************************/
+void handle_unit_battlegroup(struct player *pplayer,
+                            int unit_id, int battlegroup)
+{
+  struct unit *punit = player_find_unit_by_id(pplayer, unit_id);
+
+  if (punit) {
+    punit->battlegroup = CLIP(-1, battlegroup, MAX_NUM_BATTLEGROUPS);
+  }
+}
+
+/**************************************************************************
 ...
 **************************************************************************/
 void handle_unit_autosettlers(struct player *pplayer, int unit_id)
Index: server/savegame.c
===================================================================
--- server/savegame.c   (revision 11150)
+++ server/savegame.c   (working copy)
@@ -1468,6 +1468,10 @@
     punit->done_moving = secfile_lookup_bool_default(file,
        (punit->moves_left == 0), "player%d.u%d.done_moving", plrno, i);
 
+    punit->battlegroup
+      = secfile_lookup_bool_default(file, BATTLEGROUP_NONE,
+                                   "player%d.u%d.battlegroup", plrno, i);
+
     /* Load the goto information.  Older savegames will not have the
      * "go" field, so we just load the goto destination by default. */
     if (secfile_lookup_bool_default(file, TRUE,
@@ -2637,6 +2641,8 @@
                                plrno, i);
     secfile_insert_int(file, punit->fuel, "player%d.u%d.fuel",
                                plrno, i);
+    secfile_insert_int(file, punit->battlegroup,
+                      "player%d.u%d.battlegroup", plrno, i);
 
     if (punit->goto_tile) {
       secfile_insert_bool(file, TRUE, "player%d.u%d.go", plrno, i);
Index: common/unit.c
===================================================================
--- common/unit.c       (revision 11150)
+++ common/unit.c       (working copy)
@@ -1498,6 +1498,7 @@
   punit->ord_city = 0;
   set_unit_activity(punit, ACTIVITY_IDLE);
   punit->occupy = 0;
+  punit->battlegroup = BATTLEGROUP_NONE;
   punit->client.colored = FALSE;
   punit->server.vision = NULL; /* No vision. */
   punit->has_orders = FALSE;
Index: common/unit.h
===================================================================
--- common/unit.h       (revision 11150)
+++ common/unit.h       (working copy)
@@ -164,6 +164,12 @@
 
   int transported_by;
   int occupy; /* number of units that occupy transporter */
+
+  /* The battlegroup ID: defined by the client but stored by the server. */
+#define MAX_NUM_BATTLEGROUPS (4)
+#define BATTLEGROUP_NONE (-1)
+  int battlegroup;
+
   struct {
     /* Equivalent to pcity->client.color.  Only for F_CITIES units. */
     bool colored;
Index: common/packets.def
===================================================================
--- common/packets.def  (revision 11150)
+++ common/packets.def  (working copy)
@@ -241,7 +241,7 @@
   Spaceship
   Ruleset
 
-The last used packet number is 116.
+The last used packet number is 117.
 ****************************************************/
 
 
@@ -727,6 +727,8 @@
   ACTIVITY activity;
   SPECIAL activity_target;
 
+  SINT8 battlegroup;
+
   BOOL has_orders;
   UINT16 orders_length, orders_index;
   BOOL orders_repeat, orders_vigilant;
@@ -786,6 +788,11 @@
   UNIT unit_id;
 end
 
+PACKET_UNIT_BATTLEGROUP=117;cs,dsend
+  UNIT unit_id;
+  SINT8 battlegroup;
+end
+
 PACKET_UNIT_HELP_BUILD_WONDER=57;cs,dsend
   UNIT unit_id;
 end
Index: client/control.c
===================================================================
--- client/control.c    (revision 11150)
+++ client/control.c    (working copy)
@@ -47,7 +47,7 @@
 int num_units_below = MAX_NUM_UNITS_BELOW;
 
 /* unit_focus points to the current unit in focus */
-static struct unit *punit_focus = NULL;
+static struct unit_list *pfocus_units;
 
 /* The previously focused unit.  Focus can generally be recalled on this
  * unit with keypad 5.  FIXME: this is not reset when the client
@@ -55,11 +55,13 @@
 static int previous_focus_id = -1;
 
 /* These should be set via set_hover_state() */
-int hover_unit = 0; /* id of unit hover_state applies to */
+struct unit_list *hover_units;
 enum cursor_hover_state hover_state = HOVER_NONE;
 enum unit_activity connect_activity;
 enum unit_orders goto_last_order; /* Last order for goto */
 
+struct unit_list *battlegroups[MAX_NUM_BATTLEGROUPS];
+
 /* units involved in current combat */
 static struct unit *punit_attacking = NULL;
 static struct unit *punit_defending = NULL;
@@ -86,8 +88,15 @@
 **************************************************************************/
 void control_init(void)
 {
+  int i;
+
   caravan_arrival_queue = genlist_new();
   diplomat_arrival_queue = genlist_new();
+  hover_units = unit_list_new();
+  pfocus_units = unit_list_new();
+  for (i = 0; i < MAX_NUM_BATTLEGROUPS; i++) {
+    battlegroups[i] = unit_list_new();
+  }
 }
 
 /**************************************************************************
@@ -95,8 +104,15 @@
 **************************************************************************/
 void control_done(void)
 {
+  int i;
+
   genlist_free(caravan_arrival_queue);
   genlist_free(diplomat_arrival_queue);
+  unit_list_free(hover_units);
+  unit_list_free(pfocus_units);
+  for (i = 0; i < MAX_NUM_BATTLEGROUPS; i++) {
+    unit_list_free(battlegroups[i]);
+  }
 }
 
 /**************************************************************************
@@ -105,28 +121,54 @@
     activity => The connect activity (ACTIVITY_ROAD, etc.)
     order => The last order (ORDER_BUILD_CITY, ORDER_LAST, etc.)
 **************************************************************************/
-void set_hover_state(struct unit *punit, enum cursor_hover_state state,
+void set_hover_state(struct unit_list *punits, enum cursor_hover_state state,
                     enum unit_activity activity,
                     enum unit_orders order)
 {
-  assert(punit != NULL || state == HOVER_NONE);
+  assert((punits && unit_list_size(punits) > 0) || state == HOVER_NONE);
   assert(state == HOVER_CONNECT || activity == ACTIVITY_LAST);
   assert(state == HOVER_GOTO || order == ORDER_LAST);
-  if (punit)
-    hover_unit = punit->id;
-  else
-    hover_unit = 0;
+  unit_list_unlink_all(hover_units);
+  if (punits) {
+    unit_list_iterate(punits, punit) {
+      unit_list_append(hover_units, punit);
+    } unit_list_iterate_end;
+  }
   hover_state = state;
   connect_activity = activity;
   goto_last_order = order;
   exit_goto_state();
 }
 
+/****************************************************************************
+  Return TRUE iff this unit is in focus.
+****************************************************************************/
+bool unit_is_in_focus(const struct unit *punit)
+{
+  return unit_list_search(get_units_in_focus(), punit);
+}
+
+/****************************************************************************
+  Return TRUE iff a unit on this tile is in focus.
+****************************************************************************/
+struct unit *get_focus_unit_on_tile(const struct tile *ptile)
+{
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (punit->tile == ptile) {
+      return punit;
+    }
+  } unit_list_iterate_end;
+
+  return NULL;
+}
+
 /**************************************************************************
 Center on the focus unit, if off-screen and auto_center_on_unit is true.
 **************************************************************************/
 void auto_center_on_focus_unit(void)
 {
+  struct unit *punit_focus = get_first_unit_in_focus();
+
   if (punit_focus && auto_center_on_unit &&
       !tile_visible_and_not_on_border_mapcanvas(punit_focus->tile)) {
     center_tile_mapcanvas(punit_focus->tile);
@@ -143,21 +185,27 @@
 **************************************************************************/
 void set_unit_focus(struct unit *punit)
 {
-  struct unit *punit_old_focus = punit_focus;
+  struct unit *punit_old_focus = get_first_unit_in_focus();
 
   if (punit && game.player_ptr && punit->owner != game.player_ptr) {
     /* Callers should make sure this never happens. */
     return;
   }
 
-  if (punit != punit_focus) {
+  if (punit != punit_old_focus) {
     store_focus();
   }
 
   /*
-   *  This should be the ONLY place we _modify_ punit_focus.
+   *  This should be the ONLY place we _modify_ pfocus_units.
    */
-  punit_focus = punit;
+  unit_list_iterate(pfocus_units, punit_old) {
+    refresh_unit_mapcanvas(punit_old, punit_old->tile, TRUE, FALSE);
+  } unit_list_iterate_end;
+  unit_list_unlink_all(pfocus_units);
+  if (punit) {
+    unit_list_append(pfocus_units, punit);
+  }
 
   if (!can_client_change_view()) {
     /* This function can be called to set the focus to NULL when
@@ -191,10 +239,35 @@
                           TRUE, FALSE);
   }
 
-  update_unit_info_label(punit);
+  update_unit_info_label(pfocus_units);
   update_menus();
 }
 
+void add_unit_focus(struct unit *punit)
+{
+  if (punit && game.player_ptr && punit->owner != game.player_ptr) {
+    /* Callers should make sure this never happens. */
+    return;
+  }
+  if (!punit || !can_client_change_view()) {
+    return;
+  }
+
+  unit_list_append(pfocus_units, punit);
+  punit->focus_status = FOCUS_AVAIL;
+  refresh_unit_mapcanvas(punit, punit->tile, TRUE, FALSE);
+  if (unit_has_orders(punit)) {
+    /* Clear the focus unit's orders. */
+    request_orders_cleared(punit);
+  }
+  if (punit->activity != ACTIVITY_IDLE || punit->ai.control)  {
+    punit->ai.control = FALSE;
+    refresh_unit_city_dialogs(punit);
+    request_new_unit_activity(punit, ACTIVITY_IDLE);
+  }
+  update_menus();
+}
+
 /**************************************************************************
  The only difference is that here we draw the "cross".
 **************************************************************************/
@@ -212,6 +285,8 @@
 **************************************************************************/
 static void store_focus(void)
 {
+  struct unit *punit_focus = get_first_unit_in_focus();
+
   if (punit_focus) {
     previous_focus_id = punit_focus->id;
   }
@@ -228,25 +303,61 @@
   if (!game.player_ptr || !can_client_change_view()) {
     return;
   }
-  if (!punit_focus
-      || (punit_focus->activity != ACTIVITY_IDLE
-         && !unit_has_orders(punit_focus)
-         && punit_focus->activity != ACTIVITY_GOTO)
-      || punit_focus->done_moving
-      || punit_focus->moves_left == 0 
-      || punit_focus->ai.control) {
+  if (get_num_units_in_focus() == 0) {
     advance_unit_focus();
+  } else {
+    bool alldone = TRUE;
+
+    unit_list_iterate(get_units_in_focus(), punit) {
+      if ((punit->activity != ACTIVITY_IDLE
+          && !unit_has_orders(punit)
+          && punit->activity != ACTIVITY_GOTO)
+         || punit->done_moving
+         || punit->moves_left == 0 
+         || punit->ai.control) {
+
+      } else {
+       alldone = FALSE;
+       break;
+      }
+    } unit_list_iterate_end;
+    if (alldone) {
+      advance_unit_focus();
+    }
   }
 }
 
 /**************************************************************************
+  Returns the first unit in the focus list, or NULL.
+
+  This function is a hack; anybody that uses it probably needs to be
+  redesigned to treat all focus units equally.
+**************************************************************************/
+struct unit *get_first_unit_in_focus(void)
+{
+  if (unit_list_size(pfocus_units) > 0) {
+    return unit_list_get(pfocus_units, 0);
+  } else {
+    return NULL;
+  }
+}
+
+/**************************************************************************
 ...
 **************************************************************************/
-struct unit *get_unit_in_focus(void)
+struct unit_list *get_units_in_focus(void)
 {
-  return punit_focus;
+  return pfocus_units;
 }
 
+/****************************************************************************
+  Return the number of units currently in focus (0 or more).
+****************************************************************************/
+int get_num_units_in_focus(void)
+{
+  return unit_list_size(pfocus_units);
+}
+
 /**************************************************************************
  This function may be called from packhand.c, via update_unit_focus(),
  as a result of packets indicating change in activity for a unit. Also
@@ -256,7 +367,7 @@
 **************************************************************************/
 void advance_unit_focus(void)
 {
-  struct unit *punit_old_focus = punit_focus;
+  const int num_units_in_old_focus = get_num_units_in_focus();
   struct unit *candidate = find_best_focus_candidate(FALSE);
 
   if (!game.player_ptr || !can_client_change_view()) {
@@ -269,6 +380,16 @@
     return;
   }
 
+  unit_list_iterate(get_units_in_focus(), punit) {
+    /* 
+     * Is the unit which just lost focus a non-AI unit? If yes this
+     * enables the auto end turn. 
+     */
+    if (!punit->ai.control) {
+      non_ai_unit_focus = TRUE;
+    }
+  } unit_list_iterate_end;
+
   if(!candidate) {
     /* First try for "waiting" units. */
     unit_list_iterate(game.player_ptr->units, punit) {
@@ -287,20 +408,15 @@
   set_unit_focus(candidate);
 
   /* 
-   * Is the unit which just lost focus a non-AI unit? If yes this
-   * enables the auto end turn. 
-   */
-  if (punit_old_focus && !punit_old_focus->ai.control) {
-    non_ai_unit_focus = TRUE;
-  }
-
-  /* 
    * Handle auto-turn-done mode: If a unit was in focus (did move),
    * but now none are (no more to move) and there was at least one
    * non-AI unit this turn which was focused, then fake a Turn Done
    * keypress.
    */
-  if (auto_turn_done && punit_old_focus && !punit_focus && non_ai_unit_focus) {
+  if (auto_turn_done
+      && num_units_in_old_focus > 0
+      && get_num_units_in_focus() == 0
+      && non_ai_unit_focus) {
     key_end_turn();
   }
 }
@@ -312,9 +428,8 @@
 **************************************************************************/
 static struct unit *find_best_focus_candidate(bool accept_current)
 {
-  struct unit *best_candidate;
-  int best_dist = 99999;
   struct tile *ptile;
+  struct unit *pfirst;
 
   if (!game.player_ptr
       || !is_player_phase(game.player_ptr, game.info.phase)) {
@@ -322,29 +437,27 @@
     return NULL;
   }
 
-  if (punit_focus)  {
-    ptile = punit_focus->tile;
+  if ((pfirst = get_first_unit_in_focus())) {
+    ptile = pfirst->tile;
   } else {
     ptile = get_center_tile_mapcanvas();
   }
 
-  best_candidate = NULL;
-  unit_list_iterate(game.player_ptr->units, punit) {
-    if ((punit != punit_focus || accept_current)
-      && punit->focus_status == FOCUS_AVAIL
-      && punit->activity == ACTIVITY_IDLE
-       && !unit_has_orders(punit)
-      && punit->moves_left > 0
-      && !punit->done_moving
-      && !punit->ai.control) {
-        int d = sq_map_distance(punit->tile, ptile);
-        if (d < best_dist) {
-          best_candidate = punit;
-          best_dist = d;
-        }
-    }
-  } unit_list_iterate_end;
-  return best_candidate;
+  iterate_outward(ptile, FC_INFINITY, ptile2) {
+    unit_list_iterate(ptile2->units, punit) {
+      if ((!unit_is_in_focus(punit) || accept_current)
+         && punit->focus_status == FOCUS_AVAIL
+         && punit->activity == ACTIVITY_IDLE
+         && !unit_has_orders(punit)
+         && punit->moves_left > 0
+         && !punit->done_moving
+         && !punit->ai.control) {
+       return punit;
+      }
+    } unit_list_iterate_end;
+  } iterate_outward_end;
+
+  return NULL;
 }
 
 /**************************************************************************
@@ -353,6 +466,7 @@
 struct unit *find_visible_unit(struct tile *ptile)
 {
   struct unit *panyowned = NULL, *panyother = NULL, *ptptother = NULL;
+  struct unit *pfocus;
 
   /* If no units here, return nothing. */
   if (unit_list_size(ptile->units)==0) {
@@ -374,10 +488,8 @@
   }
 
   /* If the unit in focus is at this tile, show that on top */
-  if (punit_focus && same_pos(punit_focus->tile, ptile)) {
-    unit_list_iterate(ptile->units, punit)
-      if(punit == punit_focus) return punit;
-    unit_list_iterate_end;
+  if ((pfocus = get_focus_unit_on_tile(ptile))) {
+    return pfocus;
   }
 
   /* If a city is here, return nothing (unit hidden by city). */
@@ -418,30 +530,20 @@
 **************************************************************************/
 double blink_active_unit(void)
 {
-  static struct unit *pblinking_unit;
   static struct timer *blink_timer = NULL;
-
   const double blink_time = get_focus_unit_toggle_timeout(tileset);
-  struct unit *punit = punit_focus;
-  bool need_update = FALSE;
 
-  if (punit) {
-    if (punit != pblinking_unit) {
-      pblinking_unit = punit;
-      reset_focus_unit_state(tileset);
-      need_update = TRUE;
-    } else {
-      if (read_timer_seconds(blink_timer) > blink_time) {
-       toggle_focus_unit_state(tileset);
-       need_update = TRUE;
-      }
-    }
-    if (need_update) {
+  if (get_num_units_in_focus() > 0) {
+    if (!blink_timer || read_timer_seconds(blink_timer) > blink_time) {
+      toggle_focus_unit_state(tileset);
+
       /* If we lag, we don't try to catch up.  Instead we just start a
        * new blink_time on every update. */
       blink_timer = renew_timer_start(blink_timer, TIMER_USER, TIMER_ACTIVE);
 
-      refresh_unit_mapcanvas(punit, punit->tile, FALSE, TRUE);
+      unit_list_iterate(get_units_in_focus(), punit) {
+       refresh_unit_mapcanvas(punit, punit->tile, FALSE, TRUE);
+      } unit_list_iterate_end;
     }
 
     return blink_time - read_timer_seconds(blink_timer);
@@ -501,18 +603,21 @@
   be enough information to know whether to redraw -- instead redraw every
   time.  (Could store enough info to know, but is it worth it?)
 **************************************************************************/
-void update_unit_pix_label(struct unit *punit)
+void update_unit_pix_label(struct unit_list *punitlist)
 {
   int i;
 
   /* Check for any change in the unit's state.  This assumes that a unit's
    * orders cannot be changed directly but must be removed and then reset. */
-  if (punit && get_client_state() != CLIENT_GAME_OVER_STATE) {
+  if (unit_list_size(punitlist) > 0
+      && get_client_state() != CLIENT_GAME_OVER_STATE) {
     /* There used to be a complicated and bug-prone check here to see if
      * the unit had actually changed.  This was misguided since the stacked
      * units (below) are redrawn in any case.  Unless we write a general
      * system for unit updates here we might as well just redraw it every
      * time. */
+    struct unit *punit = unit_list_get(punitlist, 0);
+
     set_unit_icon(-1, punit);
 
     i = 0;                     /* index into unit_below_canvas */
@@ -550,7 +655,8 @@
   punit_attacking = pattacker;
   punit_defending = pdefender;
 
-  if (punit_attacking == punit_focus || punit_defending == punit_focus) {
+  if (unit_is_in_focus(punit_attacking)
+      || unit_is_in_focus(punit_defending)) {
     /* If one of the units is the focus unit, make sure hidden-focus is
      * disabled.  We don't just do this as a check later because then
      * with a blinking unit it would just disappear again right after the
@@ -675,15 +781,15 @@
 **************************************************************************/
 void request_unit_goto(enum unit_orders last_order)
 {
-  struct unit *punit = punit_focus;
+  struct unit_list *punits = get_units_in_focus();
 
-  if (!punit)
+  if (unit_list_size(punits) == 0)
     return;
 
   if (hover_state != HOVER_GOTO) {
-    set_hover_state(punit, HOVER_GOTO, ACTIVITY_LAST, last_order);
-    update_unit_info_label(punit);
-    enter_goto_state(punit);
+    set_hover_state(punits, HOVER_GOTO, ACTIVITY_LAST, last_order);
+    update_unit_info_label(punits);
+    enter_goto_state(punits);
     create_line_at_mouse_pos();
   } else {
     assert(goto_is_active());
@@ -761,16 +867,18 @@
 **************************************************************************/
 void request_unit_connect(enum unit_activity activity)
 {
-  if (!punit_focus || !can_unit_do_connect(punit_focus, activity)) {
+  if (get_num_units_in_focus() == 0
+      || !can_unit_do_connect(get_first_unit_in_focus(), activity)) {
     return;
   }
 
   if (hover_state != HOVER_CONNECT || connect_activity != activity) {
     /* Enter or change the hover connect state. */
-    set_hover_state(punit_focus, HOVER_CONNECT, activity, ORDER_LAST);
-    update_unit_info_label(punit_focus);
+    set_hover_state(get_units_in_focus(), HOVER_CONNECT,
+                   activity, ORDER_LAST);
+    update_unit_info_label(get_units_in_focus());
 
-    enter_goto_state(punit_focus);
+    enter_goto_state(get_units_in_focus());
     create_line_at_mouse_pos();
   } else {
     assert(goto_is_active());
@@ -1065,36 +1173,59 @@
 /**************************************************************************
  Explode nuclear at a tile without enemy units
 **************************************************************************/
-void request_unit_nuke(struct unit *punit)
+void request_unit_nuke(struct unit_list *punits)
 {
-  if(!unit_flag(punit, F_NUCLEAR)) {
-    create_event(punit->tile, E_BAD_COMMAND,
-                _("Only nuclear units can do this."));
+  bool can = FALSE;
+  struct tile *offender = NULL;
+
+  if (unit_list_size(punits) == 0) {
     return;
   }
-  if(punit->moves_left == 0)
-    do_unit_nuke(punit);
-  else {
-    set_hover_state(punit, HOVER_NUKE, ACTIVITY_LAST, ORDER_LAST);
-    update_unit_info_label(punit);
+  unit_list_iterate(punits, punit) {
+    if (unit_flag(punit, F_NUCLEAR)) {
+      can = TRUE;
+      break;
+    }
+    if (!offender) { /* Take first offender tile/unit */
+      offender = punit->tile;
+    }
+  } unit_list_iterate_end;
+  if (can) {
+    set_hover_state(punits, HOVER_NUKE, ACTIVITY_LAST, ORDER_LAST);
+    update_unit_info_label(punits);
+  } else {
+    create_event(offender, E_BAD_COMMAND,
+                _("Only nuclear units can do this."));
   }
 }
 
 /**************************************************************************
 ...
 **************************************************************************/
-void request_unit_paradrop(struct unit *punit)
+void request_unit_paradrop(struct unit_list *punits)
 {
-  if(!unit_flag(punit, F_PARATROOPERS)) {
-    create_event(punit->tile, E_BAD_COMMAND,
-                _("Only paratrooper units can do this."));
+  bool can = FALSE;
+  struct tile *offender = NULL;
+
+  if (unit_list_size(punits) == 0) {
     return;
   }
-  if(!can_unit_paradrop(punit))
-    return;
-
-  set_hover_state(punit, HOVER_PARADROP, ACTIVITY_LAST, ORDER_LAST);
-  update_unit_info_label(punit);
+  unit_list_iterate(punits, punit) {
+    if (can_unit_paradrop(punit)) {
+      can = TRUE;
+      break;
+    }
+    if (!offender) { /* Take first offender tile/unit */
+      offender = punit->tile;
+    }
+  } unit_list_iterate_end;
+  if (can) {
+    set_hover_state(punits, HOVER_PARADROP, ACTIVITY_LAST, ORDER_LAST);
+    update_unit_info_label(punits);
+  } else {
+    create_event(offender, E_BAD_COMMAND,
+                _("Only paratrooper units can do this."));
+  }
 }
 
 /**************************************************************************
@@ -1102,15 +1233,16 @@
 **************************************************************************/
 void request_unit_patrol(void)
 {
-  struct unit *punit = punit_focus;
+  struct unit_list *punits = get_units_in_focus();
 
-  if (!punit)
+  if (unit_list_size(punits) == 0) {
     return;
+  }
 
   if (hover_state != HOVER_PATROL) {
-    set_hover_state(punit, HOVER_PATROL, ACTIVITY_LAST, ORDER_LAST);
-    update_unit_info_label(punit);
-    enter_goto_state(punit);
+    set_hover_state(punits, HOVER_PATROL, ACTIVITY_LAST, ORDER_LAST);
+    update_unit_info_label(punits);
+    enter_goto_state(punits);
     create_line_at_mouse_pos();
   } else {
     assert(goto_is_active());
@@ -1407,8 +1539,10 @@
 **************************************************************************/
 void request_center_focus_unit(void)
 {
-  if (punit_focus) {
-    center_tile_mapcanvas(punit_focus->tile);
+  struct unit *punit = get_first_unit_in_focus();
+
+  if (punit) {
+    center_tile_mapcanvas(punit->tile);
   }
 }
 
@@ -1418,9 +1552,8 @@
 void request_unit_wait(struct unit *punit)
 {
   punit->focus_status=FOCUS_WAIT;
-  if (punit == punit_focus) {
+  if (unit_is_in_focus(punit)) {
     advance_unit_focus();
-    /* set_unit_focus(punit_focus); */  /* done in advance_unit_focus */
   }
 }
 
@@ -1429,10 +1562,11 @@
 **************************************************************************/
 void request_unit_move_done(void)
 {
-  if (punit_focus) {
-    punit_focus->focus_status = FOCUS_DONE;
+  if (get_num_units_in_focus() > 0) {
+    unit_list_iterate(get_units_in_focus(), punit) {
+      punit->focus_status = FOCUS_DONE;
+    } unit_list_iterate_end;
     advance_unit_focus();
-    /* set_unit_focus(punit_focus); */  /* done in advance_unit_focus */
   }
 }
 
@@ -1504,7 +1638,9 @@
     update_city_description(dst_tile->city);
   }
 
-  if (punit_focus == punit) update_menus();
+  if (unit_is_in_focus(punit)) {
+    update_menus();
+  }
 }
 
 /**************************************************************************
@@ -1513,10 +1649,12 @@
 void do_map_click(struct tile *ptile, enum quickselect_type qtype)
 {
   struct city *pcity = tile_get_city(ptile);
-  struct unit *punit = player_find_unit_by_id(game.player_ptr, hover_unit);
+  struct unit_list *punits = hover_units;
   bool maybe_goto = FALSE;
+  bool possible = FALSE;
+  struct tile *offender = NULL;
 
-  if (punit && hover_state != HOVER_NONE) {
+  if (unit_list_size(punits) > 0 && hover_state != HOVER_NONE) {
     switch (hover_state) {
     case HOVER_NONE:
       die("well; shouldn't get here :)");
@@ -1524,32 +1662,44 @@
       do_unit_goto(ptile);
       break;
     case HOVER_NUKE:
-      if (3 * real_map_distance(punit->tile, ptile)
-         > punit->moves_left) {
-        create_event(punit->tile, E_BAD_COMMAND, _("Too far for this unit."));
+      unit_list_iterate(punits, punit) {
+       if (SINGLE_MOVE * real_map_distance(punit->tile, ptile)
+           <= punit->moves_left) {
+         possible = TRUE;
+         break;
+       }
+       offender = punit->tile;
+      } unit_list_iterate_end;
+      if (!possible) {
+       create_event(offender, E_BAD_COMMAND, _("Too far for this unit."));
       } else {
        do_unit_goto(ptile);
-       /* note that this will be executed by the server after the goto */
-       if (!pcity)
-         do_unit_nuke(punit);
+       if (!pcity) {
+         unit_list_iterate(punits, punit) {
+           /* note that this will be executed by the server after the goto */
+           do_unit_nuke(punit);
+         } unit_list_iterate_end;
+       }
       }
       break;
     case HOVER_PARADROP:
-      do_unit_paradrop_to(punit, ptile);
+      unit_list_iterate(punits, punit) {
+       do_unit_paradrop_to(punit, ptile);
+      } unit_list_iterate_end;
       break;
     case HOVER_CONNECT:
-      do_unit_connect(punit, ptile, connect_activity);
+      do_unit_connect(ptile, connect_activity);
       break;
     case HOVER_PATROL:
-      do_unit_patrol_to(punit, ptile);
+      do_unit_patrol_to(ptile);
       break;   
     }
     set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
-    update_unit_info_label(punit);
+    update_unit_info_label(get_units_in_focus());
   }
 
   /* Bypass stack or city popup if quickselect is specified. */
-  else if (qtype) {
+  else if (qtype != SELECT_POPUP && qtype != SELECT_APPEND) {
     struct unit *qunit = quickselect(ptile, qtype);
     if (qunit) {
       set_unit_focus_and_select(qunit);
@@ -1561,7 +1711,7 @@
     popup_city_dialog(pcity);
   }
   else if (unit_list_size(ptile->units) == 0 && !pcity
-           && punit_focus) {
+           && get_num_units_in_focus() > 0) {
     maybe_goto = keyboardless_goto;
   }
   else if (unit_list_size(ptile->units) == 1
@@ -1571,7 +1721,11 @@
     if (game.player_ptr == punit->owner) {
       if(can_unit_do_activity(punit, ACTIVITY_IDLE)) {
         maybe_goto = keyboardless_goto;
-       set_unit_focus_and_select(punit);
+       if (qtype == SELECT_APPEND) {
+         add_unit_focus(punit);
+       } else {
+         set_unit_focus_and_select(punit);
+       }
       }
     } else if (pcity) {
       /* Don't hide the unit in the city. */
@@ -1630,10 +1784,10 @@
    *          Any unit
    */
 
-    unit_list_iterate(ptile->units, punit)  {
-  if (game.player_ptr != punit->owner || punit == punit_focus) {
-    continue;
-  }
+  unit_list_iterate(ptile->units, punit)  {
+    if (game.player_ptr != punit->owner || unit_is_in_focus(punit)) {
+      continue;
+    }
   if (qtype == SELECT_SEA) {
     /* Transporter. */
     if (get_transporter_capacity(punit)) {
@@ -1718,22 +1872,19 @@
 **************************************************************************/
 void do_unit_goto(struct tile *ptile)
 {
-  struct unit *punit = player_find_unit_by_id(game.player_ptr, hover_unit);
+  struct tile *dest_tile;
 
-  if (hover_unit == 0 || hover_state != HOVER_GOTO)
+  if (hover_state != HOVER_GOTO) {
     return;
+  }
 
-  if (punit) {
-    struct tile *dest_tile;
-
-    draw_line(ptile);
-    dest_tile = get_line_dest();
-    if (ptile == dest_tile) {
-      send_goto_route(punit);
-    } else {
-      create_event(punit->tile, E_BAD_COMMAND,
-                  _("Didn't find a route to the destination!"));
-    }
+  draw_line(ptile);
+  dest_tile = get_line_dest();
+  if (ptile == dest_tile) {
+    send_goto_route();
+  } else {
+    create_event(ptile, E_BAD_COMMAND,
+                _("Didn't find a route to the destination!"));
   }
 
   set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
@@ -1758,17 +1909,17 @@
 /**************************************************************************
   Patrol to a location.
 **************************************************************************/
-void do_unit_patrol_to(struct unit *punit, struct tile *ptile)
+void do_unit_patrol_to(struct tile *ptile)
 {
   struct tile *dest_tile;
 
   draw_line(ptile);
   dest_tile = get_line_dest();
   if (ptile == dest_tile
-      && !is_non_allied_unit_tile(ptile, unit_owner(punit))) {
-    send_patrol_route(punit);
+      && !is_non_allied_unit_tile(ptile, game.player_ptr)) {
+    send_patrol_route();
   } else {
-    create_event(punit->tile, E_BAD_COMMAND,
+    create_event(dest_tile, E_BAD_COMMAND,
                 _("Didn't find a route to the destination!"));
   }
 
@@ -1778,23 +1929,18 @@
 /**************************************************************************
   "Connect" to the given location.
 **************************************************************************/
-void do_unit_connect(struct unit *punit, struct tile *ptile,
+void do_unit_connect(struct tile *ptile,
                     enum unit_activity activity)
 {
-  if (is_air_unit(punit)) {
-    create_event(punit->tile, E_BAD_COMMAND,
-                _("Sorry, airunit connect not yet implemented."));
+  struct tile *dest_tile;
+
+  draw_line(ptile);
+  dest_tile = get_line_dest();
+  if (same_pos(dest_tile, ptile)) {
+    send_connect_route(activity);
   } else {
-    struct tile *dest_tile;
-
-    draw_line(ptile);
-    dest_tile = get_line_dest();
-    if (same_pos(dest_tile, ptile)) {
-      send_connect_route(punit, activity);
-    } else {
-      create_event(punit->tile, E_BAD_COMMAND,
-                  _("Didn't find a route to the destination!"));
-    }
+    create_event(ptile, E_BAD_COMMAND,
+                _("Didn't find a route to the destination!"));
   }
 
   set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
@@ -1814,10 +1960,8 @@
   }
 
   if (hover_state != HOVER_NONE && !popped) {
-    struct unit *punit = player_find_unit_by_id(game.player_ptr, hover_unit);
-
     set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
-    update_unit_info_label(punit);
+    update_unit_info_label(hover_units);
 
     keyboardless_goto_button_down = FALSE;
     keyboardless_goto_active = FALSE;
@@ -1868,10 +2012,11 @@
 **************************************************************************/
 void key_unit_move(enum direction8 gui_dir)
 {
-  if (punit_focus) {
+  unit_list_iterate(get_units_in_focus(), punit) {
     enum direction8 map_dir = gui_to_map_dir(gui_dir);
-    request_move_unit_direction(punit_focus, map_dir);
-  }
+
+    request_move_unit_direction(punit, map_dir);
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -1879,9 +2024,9 @@
 **************************************************************************/
 void key_unit_build_city(void)
 {
-  if (punit_focus) {
-    request_unit_build_city(punit_focus);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    request_unit_build_city(punit);
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -1889,9 +2034,11 @@
 **************************************************************************/
 void key_unit_build_wonder(void)
 {
-  if (punit_focus && unit_flag(punit_focus, F_HELP_WONDER)) {
-    request_unit_caravan_action(punit_focus, PACKET_UNIT_HELP_BUILD_WONDER);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (unit_flag(punit, F_HELP_WONDER)) {
+      request_unit_caravan_action(punit, PACKET_UNIT_HELP_BUILD_WONDER);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -1899,9 +2046,7 @@
 **************************************************************************/
 void key_unit_connect(enum unit_activity activity)
 {
-  if (punit_focus) {
-    request_unit_connect(activity);
-  }
+  request_unit_connect(activity);
 }
 
 /**************************************************************************
@@ -1910,13 +2055,17 @@
 void key_unit_diplomat_actions(void)
 {
   struct city *pcity;          /* need pcity->id */
-  if (punit_focus
-     && is_diplomat_unit(punit_focus)
-     && (pcity = tile_get_city(punit_focus->tile))
-     && diplomat_handled_in_diplomat_dialog() != -1    /* confusing otherwise? 
*/
-     && diplomat_can_do_action(punit_focus, DIPLOMAT_ANY_ACTION,
-                              punit_focus->tile))
-     process_diplomat_arrival(punit_focus, pcity->id);
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (is_diplomat_unit(punit)
+       && (pcity = tile_get_city(punit->tile))
+       && diplomat_handled_in_diplomat_dialog() != -1    /* confusing 
otherwise? */
+       && diplomat_can_do_action(punit, DIPLOMAT_ANY_ACTION,
+                                 punit->tile)) {
+      process_diplomat_arrival(punit, pcity->id);
+      return;
+      /* FIXME: diplomat dialog for more than one unit at a time. */
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -1924,9 +2073,7 @@
 **************************************************************************/
 void key_unit_done(void)
 {
-  if (punit_focus) {
-    request_unit_move_done();
-  }
+  request_unit_move_done();
 }
 
 /**************************************************************************
@@ -1934,9 +2081,7 @@
 **************************************************************************/
 void key_unit_goto(void)
 {
-  if (punit_focus) {
-    request_unit_goto(ORDER_LAST);
-  }
+  request_unit_goto(ORDER_LAST);
 }
 
 /**************************************************************************
@@ -1944,9 +2089,7 @@
 **************************************************************************/
 void key_unit_nuke(void)
 {
-  if (punit_focus) {
-    request_unit_nuke(punit_focus);
-  }
+  request_unit_nuke(get_units_in_focus());
 }
 
 /**************************************************************************
@@ -1954,9 +2097,7 @@
 **************************************************************************/
 void key_unit_paradrop(void)
 {
-  if (punit_focus && can_unit_paradrop(punit_focus)) {
-    request_unit_paradrop(punit_focus);
-  }
+  request_unit_paradrop(get_units_in_focus());
 }
 
 /**************************************************************************
@@ -1964,9 +2105,7 @@
 **************************************************************************/
 void key_unit_patrol(void)
 {
-  if (punit_focus) {
-    request_unit_patrol();
-  }
+  request_unit_patrol();
 }
 
 /**************************************************************************
@@ -1974,9 +2113,11 @@
 **************************************************************************/
 void key_unit_traderoute(void)
 {
-  if (punit_focus && unit_flag(punit_focus, F_TRADE_ROUTE)) {
-    request_unit_caravan_action(punit_focus, PACKET_UNIT_ESTABLISH_TRADE);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (unit_flag(punit, F_TRADE_ROUTE)) {
+      request_unit_caravan_action(punit, PACKET_UNIT_ESTABLISH_TRADE);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -1984,9 +2125,9 @@
 **************************************************************************/
 void key_unit_unload_all(void)
 {
-  if (punit_focus) {
-    request_unit_unload_all(punit_focus);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    request_unit_unload_all(punit);
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -1994,9 +2135,9 @@
 **************************************************************************/
 void key_unit_wait(void)
 {
-  if (punit_focus) {
-    request_unit_wait(punit_focus);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    request_unit_wait(punit);
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2004,9 +2145,9 @@
 ***************************************************************************/
 void key_unit_wakeup_others(void)
 {
-  if (punit_focus) {
-    request_unit_wakeup(punit_focus);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    request_unit_wakeup(punit);
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2014,10 +2155,11 @@
 **************************************************************************/
 void key_unit_airbase(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_AIRBASE)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_AIRBASE);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_AIRBASE)) {
+      request_new_unit_activity(punit, ACTIVITY_AIRBASE);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2025,10 +2167,11 @@
 **************************************************************************/
 void key_unit_auto_explore(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_EXPLORE)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_EXPLORE);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_EXPLORE)) {
+      request_new_unit_activity(punit, ACTIVITY_EXPLORE);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2037,9 +2180,11 @@
 **************************************************************************/
 void key_unit_auto_settle(void)
 {
-  if (punit_focus && can_unit_do_autosettlers(punit_focus)) {
-    request_unit_autosettlers(punit_focus);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_autosettlers(punit)) {
+      request_unit_autosettlers(punit);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2047,9 +2192,9 @@
 **************************************************************************/
 void key_unit_disband(void)
 {
-  if (punit_focus) {
-    request_unit_disband(punit_focus);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    request_unit_disband(punit);
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2057,10 +2202,11 @@
 **************************************************************************/
 void key_unit_fallout(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_FALLOUT)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_FALLOUT);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_FALLOUT)) {
+      request_new_unit_activity(punit, ACTIVITY_FALLOUT);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2068,10 +2214,11 @@
 **************************************************************************/
 void key_unit_fortify(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_FORTIFYING)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_FORTIFYING);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_FORTIFYING)) {
+      request_new_unit_activity(punit, ACTIVITY_FORTIFYING);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2079,10 +2226,11 @@
 **************************************************************************/
 void key_unit_fortress(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_FORTRESS)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_FORTRESS);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_FORTRESS)) {
+      request_new_unit_activity(punit, ACTIVITY_FORTRESS);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2090,9 +2238,9 @@
 **************************************************************************/
 void key_unit_homecity(void)
 {
-  if (punit_focus) {
-    request_unit_change_homecity(punit_focus);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    request_unit_change_homecity(punit);
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2100,10 +2248,11 @@
 **************************************************************************/
 void key_unit_irrigate(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_IRRIGATE)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_IRRIGATE);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_IRRIGATE)) {
+      request_new_unit_activity(punit, ACTIVITY_IRRIGATE);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2111,10 +2260,11 @@
 **************************************************************************/
 void key_unit_mine(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_MINE)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_MINE);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_MINE)) {
+      request_new_unit_activity(punit, ACTIVITY_MINE);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2122,10 +2272,11 @@
 **************************************************************************/
 void key_unit_pillage(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_PILLAGE)) {
-    request_unit_pillage(punit_focus);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_PILLAGE)) {
+      request_unit_pillage(punit);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2133,10 +2284,11 @@
 **************************************************************************/
 void key_unit_pollution(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_POLLUTION)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_POLLUTION);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_POLLUTION)) {
+      request_new_unit_activity(punit, ACTIVITY_POLLUTION);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2144,12 +2296,13 @@
 **************************************************************************/
 void key_unit_road(void)
 {
-  if (punit_focus) {
-    if(can_unit_do_activity(punit_focus, ACTIVITY_ROAD))
-      request_new_unit_activity(punit_focus, ACTIVITY_ROAD);
-    else if(can_unit_do_activity(punit_focus, ACTIVITY_RAILROAD))
-      request_new_unit_activity(punit_focus, ACTIVITY_RAILROAD);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_ROAD)) {
+      request_new_unit_activity(punit, ACTIVITY_ROAD);
+    } else if (can_unit_do_activity(punit, ACTIVITY_RAILROAD)) {
+      request_new_unit_activity(punit, ACTIVITY_RAILROAD);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2157,10 +2310,11 @@
 **************************************************************************/
 void key_unit_sentry(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_SENTRY)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_SENTRY);
-  }
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_SENTRY)) {
+      request_new_unit_activity(punit, ACTIVITY_SENTRY);
+    }
+  } unit_list_iterate_end;
 }
 
 /**************************************************************************
@@ -2168,12 +2322,67 @@
 **************************************************************************/
 void key_unit_transform(void)
 {
-  if (punit_focus &&
-      can_unit_do_activity(punit_focus, ACTIVITY_TRANSFORM)) {
-    request_new_unit_activity(punit_focus, ACTIVITY_TRANSFORM);
+  unit_list_iterate(get_units_in_focus(), punit) {
+    if (can_unit_do_activity(punit, ACTIVITY_TRANSFORM)) {
+      request_new_unit_activity(punit, ACTIVITY_TRANSFORM);
+    }
+  } unit_list_iterate_end;
+}
+
+/****************************************************************************
+  Assign all focus units to this battlegroup.
+****************************************************************************/
+void key_unit_assign_battlegroup(int battlegroup)
+{
+  if (game.player_ptr && can_client_issue_orders()
+      && battlegroups >= 0 && battlegroup < MAX_NUM_BATTLEGROUPS) {
+    /* FIXME: this is moderately inefficient since usually we just keep
+     * the same units in the battlegroup. */
+    unit_list_iterate(battlegroups[battlegroup], punit) {
+      punit->battlegroup = BATTLEGROUP_NONE;
+      dsend_packet_unit_battlegroup(&aconnection,
+                                   punit->id, BATTLEGROUP_NONE);
+    } unit_list_iterate_end;
+    unit_list_unlink_all(battlegroups[battlegroup]);
+    unit_list_iterate(get_units_in_focus(), punit) {
+      if (punit->battlegroup >= 0
+         && punit->battlegroup < MAX_NUM_BATTLEGROUPS) {
+       unit_list_unlink(battlegroups[punit->battlegroup], punit);
+      }
+      punit->battlegroup = battlegroup;
+      dsend_packet_unit_battlegroup(&aconnection,
+                                   punit->id, battlegroup);
+      unit_list_append(battlegroups[battlegroup], punit);
+    } unit_list_iterate_end;
   }
 }
 
+/****************************************************************************
+  Bring this battlegroup into focus.
+****************************************************************************/
+void key_unit_select_battlegroup(int battlegroup)
+{
+  if (game.player_ptr && can_client_change_view()
+      && battlegroups >= 0 && battlegroup < MAX_NUM_BATTLEGROUPS) {
+    int i = 0;
+
+    if (unit_list_size(battlegroups[battlegroup]) == 0) {
+      set_unit_focus(NULL);
+      return;
+    }
+
+    /* FIXME: this is very inefficient and can be improved. */
+    unit_list_iterate(battlegroups[battlegroup], punit) {
+      if (i == 0) {
+       set_unit_focus(punit);
+      } else {
+       add_unit_focus(punit);
+      }
+      i++;
+    } unit_list_iterate_end;
+  }
+}
+
 /**************************************************************************
   Toggle drawing of city outlines.
 **************************************************************************/
@@ -2324,10 +2533,9 @@
 **************************************************************************/
 void key_quickselect(enum quickselect_type qtype)
 {
-  struct unit *punit;
+  unit_list_iterate(get_units_in_focus(), punit) {
+    struct unit *punit2 = quickselect(punit->tile, qtype);
 
-  if(punit_focus) {
-    punit = quickselect(punit_focus->tile, qtype);
-    set_unit_focus_and_select(punit);
-  }
+    set_unit_focus_and_select(punit2);
+  } unit_list_iterate_end;
 }
Index: client/gui-gtk-2.0/gotodlg.c
===================================================================
--- client/gui-gtk-2.0/gotodlg.c        (revision 11150)
+++ client/gui-gtk-2.0/gotodlg.c        (working copy)
@@ -82,11 +82,9 @@
       struct city *pdestcity = get_selected_city();
 
       if (pdestcity) {
-        struct unit *punit = get_unit_in_focus();
-
-        if (punit) {
+       unit_list_iterate(get_units_in_focus(), punit) {
           request_unit_airlift(punit, pdestcity);
-        }
+        } unit_list_iterate_end;
       }
     }
     break;
@@ -96,11 +94,9 @@
       struct city *pdestcity = get_selected_city();
 
       if (pdestcity) {
-        struct unit *punit = get_unit_in_focus();
-
-        if (punit) {
+       unit_list_iterate(get_units_in_focus(), punit) {
           send_goto_tile(punit, pdestcity->tile);
-        }
+        } unit_list_iterate_end;
       }
     }
     break;
@@ -206,7 +202,7 @@
 *****************************************************************/
 void popup_goto_dialog(void)
 {
-  if (!can_client_issue_orders() || !get_unit_in_focus()) {
+  if (!can_client_issue_orders() || get_num_units_in_focus() == 0) {
     return;
   }
 
@@ -270,9 +266,17 @@
   struct city *pdestcity;
 
   if((pdestcity = get_selected_city())) {
-    struct unit *punit = get_unit_in_focus();
+    bool can_airlift = FALSE;
+
+    unit_list_iterate(get_units_in_focus(), punit) {
+      if (unit_can_airlift_to(punit, pdestcity)) {
+       can_airlift = TRUE;
+       break;
+      }
+    } unit_list_iterate_end;
+
     center_tile_mapcanvas(pdestcity->tile);
-    if(punit && unit_can_airlift_to(punit, pdestcity)) {
+    if (can_airlift) {
       gtk_dialog_set_response_sensitive(GTK_DIALOG(dshell), CMD_AIRLIFT, TRUE);
       return;
     }
Index: client/gui-gtk-2.0/mapview.c
===================================================================
--- client/gui-gtk-2.0/mapview.c        (revision 11150)
+++ client/gui-gtk-2.0/mapview.c        (working copy)
@@ -166,9 +166,11 @@
   Also calls update_unit_pix_label() to update the icons for units on this
   square.
 **************************************************************************/
-void update_unit_info_label(struct unit *punit)
+void update_unit_info_label(struct unit_list *punitlist)
 {
   GtkWidget *label;
+  struct unit *punit = (unit_list_size(punitlist) > 0
+                       ? unit_list_get(punitlist, 0) : NULL);
 
   label = gtk_frame_get_label_widget(GTK_FRAME(unit_info_frame));
   gtk_label_set_text(GTK_LABEL(label),
@@ -178,9 +180,21 @@
                     get_unit_info_label_text2(punit));
 
   if(punit) {
-    if (hover_unit != punit->id)
-      set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
+    if (unit_list_size(hover_units) > 0) {
+      bool is_hover = FALSE;
 
+      unit_list_iterate(hover_units, punit2) {
+       if (punit2 == punit) {
+         is_hover = TRUE;
+         break;
+       }
+      } unit_list_iterate_end;
+
+      if (!is_hover) {
+       set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
+      }
+    }
+
     switch (hover_state) {
     case HOVER_NONE:
       gdk_window_set_cursor (root_window, NULL);
@@ -202,7 +216,7 @@
   } else {
     gdk_window_set_cursor(root_window, NULL);
   }
-  update_unit_pix_label(punit);
+  update_unit_pix_label(punitlist);
 }
 
 
Index: client/gui-gtk-2.0/gui_main.c
===================================================================
--- client/gui-gtk-2.0/gui_main.c       (revision 11150)
+++ client/gui-gtk-2.0/gui_main.c       (working copy)
@@ -440,7 +440,40 @@
       }
     }
 
+    assert(MAX_NUM_BATTLEGROUPS == 4);
     switch (ev->keyval) {
+    case GDK_1:
+      if (ev->state & GDK_CONTROL_MASK) {
+       key_unit_assign_battlegroup(0);
+      } else {
+       key_unit_select_battlegroup(0);
+      }
+      break;
+
+    case GDK_2:
+      if (ev->state & GDK_CONTROL_MASK) {
+       key_unit_assign_battlegroup(1);
+      } else {
+       key_unit_select_battlegroup(1);
+      }
+      break;
+
+    case GDK_3:
+      if (ev->state & GDK_CONTROL_MASK) {
+       key_unit_assign_battlegroup(2);
+      } else {
+       key_unit_select_battlegroup(2);
+      }
+      break;
+
+    case GDK_4:
+      if (ev->state & GDK_CONTROL_MASK) {
+       key_unit_assign_battlegroup(3);
+      } else {
+       key_unit_select_battlegroup(3);
+      }
+      break;
+
       case GDK_KP_Up:
       case GDK_8:
       case GDK_KP_8:
@@ -460,25 +493,21 @@
        break;
 
       case GDK_KP_Page_Down:
-      case GDK_3:
       case GDK_KP_3:
        key_unit_move(DIR8_SOUTHEAST);
        break;
 
       case GDK_KP_Down:
-      case GDK_2:
       case GDK_KP_2:
        key_unit_move(DIR8_SOUTH);
        break;
 
       case GDK_KP_End:
-      case GDK_1:
       case GDK_KP_1:
        key_unit_move(DIR8_SOUTHWEST);
        break;
 
       case GDK_KP_Left:
-      case GDK_4:
       case GDK_KP_4:
        key_unit_move(DIR8_WEST);
        break;
@@ -691,8 +720,8 @@
    * but we have to make the *internal* state consistent). */
   gtk_widget_hide(more_arrow_pixmap);
   set_unit_icons_more_arrow(FALSE);
-  set_unit_icon(-1, get_unit_in_focus());
-  update_unit_pix_label(get_unit_in_focus());
+  set_unit_icon(-1, get_first_unit_in_focus());
+  update_unit_pix_label(get_units_in_focus());
 }
 
 /**************************************************************************
@@ -1488,7 +1517,7 @@
   struct unit *punit;
 
   if (i == -1) {
-    if ((punit = get_unit_in_focus())) {
+    if ((punit = get_first_unit_in_focus())) {
       /* Clicking on the currently selected unit will center it. */
       center_tile_mapcanvas(punit->tile);
     }
Index: client/gui-gtk-2.0/menu.c
===================================================================
--- client/gui-gtk-2.0/menu.c   (revision 11150)
+++ client/gui-gtk-2.0/menu.c   (working copy)
@@ -395,25 +395,28 @@
                                 guint callback_action, GtkWidget *widget)
 {
   switch(callback_action) {
-   case MENU_ORDER_BUILD_CITY:
-    if (get_unit_in_focus()) {
-      struct unit *punit = get_unit_in_focus();
+  case MENU_ORDER_BUILD_CITY:
+    unit_list_iterate(get_units_in_focus(), punit) {
+      /* FIXME: this can provide different actions for different units...
+       * not good! */
       /* Enable the button for adding to a city in all cases, so we
         get an eventual error message from the server if we try. */
       if (can_unit_add_or_build_city(punit)) {
-       key_unit_build_city();
+       request_unit_build_city(punit);
       } else {
-       key_unit_build_wonder();
+       request_unit_caravan_action(punit, PACKET_UNIT_HELP_BUILD_WONDER);
       }
-    }
+    } unit_list_iterate_end;
     break;
-   case MENU_ORDER_ROAD:
-    if (get_unit_in_focus()) {
-      if (unit_can_est_traderoute_here(get_unit_in_focus()))
+  case MENU_ORDER_ROAD:
+    unit_list_iterate(get_units_in_focus(), punit) {
+      /* FIXME: this can provide different actions for different units...
+       * not good! */
+      if (unit_can_est_traderoute_here(punit))
        key_unit_traderoute();
       else
        key_unit_road();
-    }
+    } unit_list_iterate_end;
     break;
    case MENU_ORDER_IRRIGATE:
     key_unit_irrigate();
@@ -424,24 +427,28 @@
    case MENU_ORDER_TRANSFORM:
     key_unit_transform();
     break;
-   case MENU_ORDER_FORTRESS:
-    if (get_unit_in_focus()) {
-      if (can_unit_do_activity(get_unit_in_focus(), ACTIVITY_FORTRESS))
+  case MENU_ORDER_FORTRESS:
+    unit_list_iterate(get_units_in_focus(), punit) {
+      /* FIXME: this can provide different actions for different units...
+       * not good! */
+      if (can_unit_do_activity(punit, ACTIVITY_FORTRESS))
        key_unit_fortress();
       else
        key_unit_fortify();
-    }
+    } unit_list_iterate_end;
     break;
    case MENU_ORDER_AIRBASE:
     key_unit_airbase(); 
     break;
    case MENU_ORDER_POLLUTION:
-    if (get_unit_in_focus()) {
-      if (can_unit_paradrop(get_unit_in_focus()))
+    unit_list_iterate(get_units_in_focus(), punit) {
+      /* FIXME: this can provide different actions for different units...
+       * not good! */
+      if (can_unit_paradrop(punit))
        key_unit_paradrop();
       else
        key_unit_pollution();
-    }
+    } unit_list_iterate_end;
     break;
    case MENU_ORDER_FALLOUT:
     key_unit_fallout();
@@ -459,16 +466,22 @@
     key_unit_unload_all();
     break;
   case MENU_ORDER_LOAD:
-    request_unit_load(get_unit_in_focus(), NULL);
+    unit_list_iterate(get_units_in_focus(), punit) {
+      request_unit_load(punit, NULL);
+    } unit_list_iterate_end;
     break;
   case MENU_ORDER_UNLOAD:
-    request_unit_unload(get_unit_in_focus());
+    unit_list_iterate(get_units_in_focus(), punit) {
+      request_unit_unload(punit);
+    } unit_list_iterate_end;
     break;
    case MENU_ORDER_WAKEUP_OTHERS:
     key_unit_wakeup_others();
     break;
-   case MENU_ORDER_AUTO_SETTLER:
-     request_unit_autosettlers(get_unit_in_focus());
+  case MENU_ORDER_AUTO_SETTLER:
+    unit_list_iterate(get_units_in_focus(), punit) {
+      request_unit_autosettlers(punit);
+    } unit_list_iterate_end;
     break;
    case MENU_ORDER_AUTO_EXPLORE:
     key_unit_auto_explore();
@@ -491,15 +504,16 @@
    case MENU_ORDER_GOTO:
     key_unit_goto();
     break;
-   case MENU_ORDER_GOTO_CITY:
-    if(get_unit_in_focus())
+  case MENU_ORDER_GOTO_CITY:
+    if (get_num_units_in_focus() > 0) {
       popup_goto_dialog();
-    break;
-   case MENU_ORDER_RETURN:
-    if (get_unit_in_focus()) {
-      request_unit_return(get_unit_in_focus());
     }
     break;
+  case MENU_ORDER_RETURN:
+    unit_list_iterate(get_units_in_focus(), punit) {
+      request_unit_return(punit);
+    } unit_list_iterate_end;
+    break;
    case MENU_ORDER_DISBAND:
     key_unit_disband();
     break;
@@ -1282,7 +1296,7 @@
       return;
     }
 
-    if((punit=get_unit_in_focus())) {
+    if((punit=get_first_unit_in_focus())) {
       const char *irrfmt = _("Change to %s (_I)");
       const char *minfmt = _("Change to %s (_M)");
       const char *transfmt = _("Transf_orm to %s");
Index: client/gui-gtk-2.0/dialogs.c
===================================================================
--- client/gui-gtk-2.0/dialogs.c        (revision 11150)
+++ client/gui-gtk-2.0/dialogs.c        (working copy)
@@ -628,7 +628,7 @@
       -1);
   g_object_unref(pix);
 
-  if (punit == get_unit_in_focus()) {
+  if (unit_is_in_focus(punit)) {
     unit_select_path =
       gtk_tree_model_get_path(GTK_TREE_MODEL(unit_select_store), it);
   }
Index: client/gui-gtk-2.0/mapctrl.c
===================================================================
--- client/gui-gtk-2.0/mapctrl.c        (revision 11150)
+++ client/gui-gtk-2.0/mapctrl.c        (working copy)
@@ -253,9 +253,9 @@
     else if (ev->state & GDK_CONTROL_MASK) {
       action_button_pressed(ev->x, ev->y, SELECT_SEA);
     }
-    /* <SHIFT> + LMB: Copy Production. */
+    /* <SHIFT> + LMB: Append focus unit Production. */
     else if (ptile && (ev->state & GDK_SHIFT_MASK)) {
-      clipboard_copy_production(ptile);
+      action_button_pressed(ev->x, ev->y, SELECT_APPEND);
     }
     /* <ALT> + LMB: popit (same as middle-click) */
     /* FIXME: we need a general mechanism for letting freeciv work with
@@ -403,7 +403,8 @@
   if (can_client_change_view() && (ev->button == 3)) {
     center_tile_mapcanvas(map_pos_to_tile(xtile, ytile));
   } else if (can_client_issue_orders() && (ev->button == 1)) {
-    do_map_click(map_pos_to_tile(xtile, ytile), SELECT_POPUP);
+    do_map_click(map_pos_to_tile(xtile, ytile),
+                (ev->state & GDK_SHIFT_MASK) ? SELECT_POPUP : SELECT_APPEND);
   }
 
   return TRUE;
Index: client/text.c
===================================================================
--- client/text.c       (revision 11150)
+++ client/text.c       (working copy)
@@ -73,7 +73,7 @@
   const char *activity_text;
   struct city *pcity = ptile->city;
   struct unit *punit = find_visible_unit(ptile);
-  struct unit *pfocus_unit = get_unit_in_focus();
+  struct unit *pfocus_unit = get_first_unit_in_focus(); /* FIXME */
   const char *diplo_nation_plural_adjectives[DS_LAST] =
     {Q_("?nation:Neutral"), Q_("?nation:Hostile"),
      "" /* unused, DS_CEASEFIRE*/,
@@ -657,7 +657,7 @@
     bv_special infrastructure =
       get_tile_infrastructure_set(punit->tile, &infracount);
 
-    if (hover_unit == punit->id) {
+    if (unit_list_search(hover_units, punit)) {
       astr_add_line(&str, _("Turns to target: %d"), get_goto_turns());
     } else {
       astr_add_line(&str, "%s", unit_activity_text(punit));
Index: client/control.h
===================================================================
--- client/control.h    (revision 11150)
+++ client/control.h    (working copy)
@@ -26,30 +26,32 @@
 
 /* Selecting unit from a stack without popup. */
 enum quickselect_type {
-  SELECT_POPUP = 0, SELECT_SEA, SELECT_LAND
+  SELECT_POPUP = 0, SELECT_SEA, SELECT_LAND, SELECT_APPEND
 };
 
 void control_init(void);
 void control_done(void);
 
-extern int hover_unit; /* unit hover_state applies to */
+extern struct unit_list *hover_units; /* unit hover_state applies to */
 extern enum cursor_hover_state hover_state;
 extern enum unit_activity connect_activity;
 extern enum unit_orders goto_last_order;
 extern bool non_ai_unit_focus;
 
+extern struct unit_list *battlegroups[MAX_NUM_BATTLEGROUPS];
+
 bool can_unit_do_connect(struct unit *punit, enum unit_activity activity);
 
 void do_move_unit(struct unit *punit, struct unit *target_unit);
 void do_unit_goto(struct tile *ptile);
 void do_unit_nuke(struct unit *punit);
 void do_unit_paradrop_to(struct unit *punit, struct tile *ptile);
-void do_unit_patrol_to(struct unit *punit, struct tile *ptile);
-void do_unit_connect(struct unit *punit, struct tile *ptile,
+void do_unit_patrol_to(struct tile *ptile);
+void do_unit_connect(struct tile *ptile,
                     enum unit_activity activity);
 void do_map_click(struct tile *ptile, enum quickselect_type qtype);
 
-void set_hover_state(struct unit *punit, enum cursor_hover_state state,
+void set_hover_state(struct unit_list *punits, enum cursor_hover_state state,
                     enum unit_activity connect_activity,
                     enum unit_orders goto_last_order);
 void request_center_focus_unit(void);
@@ -69,8 +71,8 @@
 void request_unit_fortify(struct unit *punit);
 void request_unit_goto(enum unit_orders last_order);
 void request_unit_move_done(void);
-void request_unit_nuke(struct unit *punit);
-void request_unit_paradrop(struct unit *punit);
+void request_unit_nuke(struct unit_list *punits);
+void request_unit_paradrop(struct unit_list *punits);
 void request_unit_patrol(void);
 void request_unit_pillage(struct unit *punit);
 void request_unit_sentry(struct unit *punit);
@@ -103,17 +105,22 @@
 
 void wakeup_sentried_units(struct tile *ptile);
 
+bool unit_is_in_focus(const struct unit *punit);
+struct unit *get_focus_unit_on_tile(const struct tile *ptile);
 void auto_center_on_focus_unit(void);
 void advance_unit_focus(void);
-struct unit *get_unit_in_focus(void);
+struct unit *get_first_unit_in_focus(void); /* HACK: do not use if possible. */
+struct unit_list *get_units_in_focus(void);
+int get_num_units_in_focus(void);
 void set_unit_focus(struct unit *punit);
+void add_unit_focus(struct unit *punit);
 void set_unit_focus_and_select(struct unit *punit);
 void update_unit_focus(void);
 struct unit *find_visible_unit(struct tile *ptile);
 void set_units_in_combat(struct unit *pattacker, struct unit *pdefender);
 double blink_active_unit(void);
 double blink_turn_done_button(void);
-void update_unit_pix_label(struct unit *punit);
+void update_unit_pix_label(struct unit_list *punitlist);
 
 void process_caravan_arrival(struct unit *punit);
 void process_diplomat_arrival(struct unit *pdiplomat, int victim_id);
@@ -170,6 +177,8 @@
 void key_unit_unload_all(void);
 void key_unit_wait(void);
 void key_unit_wakeup_others(void);
+void key_unit_assign_battlegroup(int battlegroup);
+void key_unit_select_battlegroup(int battlegroup);
 
 /* don't change this unless you also put more entries in data/Freeciv */
 #define MAX_NUM_UNITS_BELOW 4
Index: client/include/mapview_g.h
===================================================================
--- client/include/mapview_g.h  (revision 11150)
+++ client/include/mapview_g.h  (working copy)
@@ -22,7 +22,7 @@
 #include "mapview_common.h"
 
 void update_info_label(void);
-void update_unit_info_label(struct unit *punit);
+void update_unit_info_label(struct unit_list *punitlist);
 void update_timeout_label(void);
 void update_turn_done_button(bool do_restore);
 void update_city_descriptions(void);
Index: client/goto.c
===================================================================
--- client/goto.c       (revision 11150)
+++ client/goto.c       (working copy)
@@ -49,31 +49,57 @@
   struct pf_map *map;
 };
 
+/* For each tile and each direction we store the number of lines going out 
+ * of the tile in this direction.  Since each line is undirected, we only 
+ * store the 4 lower-numbered directions for each tile; the 4 upper-numbered
+ * directions are stored as reverses from the target tile.
+ * Notes: 1. This assumes that 
+ * - there are 8 directions
+ * - out of every two opposite directions (like NORTH and SOUTH) one and 
+ *   only one has number less than 4
+ * 2. There _can_ be more than one line drawn between two tiles, because of 
+ * the waypoints. */
 static struct {
-  /* For each tile and each direction we store the number of lines going out 
-   * of the tile in this direction.  Since each line is undirected, we only 
-   * store the 4 lower-numbered directions for each tile; the 4 upper-numbered
-   * directions are stored as reverses from the target tile.
-   * Notes: 1. This assumes that 
-   * - there are 8 directions
-   * - out of every two opposite directions (like NORTH and SOUTH) one and 
-   *   only one has number less than 4
-   * 2. There _can_ be more than one line drawn between two tiles, because of 
-   * the waypoints. */
-  struct {
-    unsigned char drawn[4];
-  } *tiles;
+  unsigned char drawn[4];
+} *tiles;
+
+struct goto_map {
   int unit_id;                  /* The unit of the goto map */
   struct part *parts;
   int num_parts;
   struct pf_parameter template;
-} goto_map;
+};
 
-#define DRAWN(ptile, dir) (goto_map.tiles[(ptile)->index].drawn[dir])
+/* get 'struct goto_map_list' and related functions: */
+#define SPECLIST_TAG goto_map
+#define SPECLIST_TYPE struct goto_map
+#include "speclist.h"
+#define goto_map_list_iterate(gotolist, pgoto)                             \
+  TYPED_LIST_ITERATE(struct goto_map, gotolist, pgoto)
+#define goto_map_list_iterate_end                                          \
+  LIST_ITERATE_END
 
+/* Iterate over goto maps, filtering out dead units. */
+#define goto_map_iterate(gotolist, pgoto, punit)                           \
+  goto_map_list_iterate(gotolist, pgoto) {                                 \
+    struct unit *punit = find_unit_by_id(pgoto->unit_id);                  \
+                                                                           \
+    if (punit) {                                                           \
+      assert(unit_is_in_focus(punit));                                     \
+      {
+
+#define goto_map_iterate_end                                               \
+      }                                                                        
    \
+    }                                                                      \
+  } goto_map_list_iterate_end;
+
+static struct goto_map_list *goto_maps;
+
+#define DRAWN(ptile, dir) (tiles[(ptile)->index].drawn[dir])
+
 static void increment_drawn(struct tile *src_tile, enum direction8 dir);
 static void decrement_drawn(struct tile *src_tile, enum direction8 dir);
-static void reset_last_part(void);
+static void reset_last_part(struct goto_map *goto_map);
 
 
 /**************************************************************************
@@ -83,6 +109,31 @@
 static bool is_init = FALSE;
 static int connect_initial;
 
+/****************************************************************************
+  Create a new goto map.
+****************************************************************************/
+static struct goto_map *goto_map_new(void)
+{
+  struct goto_map *goto_map = fc_malloc(sizeof(*goto_map));
+
+  goto_map->unit_id = -1;
+  goto_map->parts = NULL;
+  goto_map->num_parts = 0;
+
+  return goto_map;
+}
+
+/****************************************************************************
+  Free an existing goto map.
+****************************************************************************/
+static void goto_map_free(struct goto_map *goto_map)
+{
+  if (goto_map->parts) {
+    free(goto_map->parts);
+  }
+  free(goto_map);
+}
+
 /********************************************************************** 
   Called once per game.
 ***********************************************************************/
@@ -92,12 +143,12 @@
     free_client_goto();
   }
 
-  goto_map.tiles = fc_malloc(MAP_INDEX_SIZE * sizeof(*goto_map.tiles));
-  goto_map.parts = NULL;
-  goto_map.num_parts = 0;
-  goto_map.unit_id = -1;
+  goto_maps = goto_map_list_new();
+
+  tiles = fc_malloc(MAP_INDEX_SIZE * sizeof(*tiles));
   whole_map_iterate(ptile) {
     int dir;
+
     for (dir = 0; dir < 4; dir++) {
       DRAWN(ptile, dir) = 0;
     }
@@ -112,12 +163,13 @@
 void free_client_goto(void)
 {
   if (is_init) {
-    free(goto_map.tiles);
-    if (goto_map.parts) {
-      free(goto_map.parts);
-    }
+    goto_map_list_iterate(goto_maps, goto_map) {
+      goto_map_free(goto_map);
+    } goto_map_list_iterate_end;
+    goto_map_list_unlink_all(goto_maps);
+    goto_map_list_free(goto_maps);
 
-    memset(&goto_map, 0, sizeof(goto_map));
+    free(tiles);
 
     is_init = FALSE;
   }
@@ -127,20 +179,20 @@
   Change the destination of the last part to the given position if a
   path can be found. If not the destination is set to the start.
 ***********************************************************************/
-static void update_last_part(struct tile *ptile)
+static void update_last_part(struct goto_map *goto_map, struct tile *ptile)
 {
-  struct part *p = &goto_map.parts[goto_map.num_parts - 1];
+  struct part *p = &goto_map->parts[goto_map->num_parts - 1];
   struct pf_path *new_path;
   struct tile *old_tile = p->start_tile;
   int i, start_index = 0;
 
   freelog(LOG_DEBUG, "update_last_part(%d,%d) old (%d,%d)-(%d,%d)",
-          TILE_XY(ptile), TILE_XY(p->start_tile), TILE_XY(p->end_tile));
+         TILE_XY(ptile), TILE_XY(p->start_tile), TILE_XY(p->end_tile));
   new_path = pf_get_path(p->map, ptile);
 
   if (!new_path) {
     freelog(PATH_LOG_LEVEL, "  no path found");
-    reset_last_part();
+    reset_last_part(goto_map);
     return;
   }
 
@@ -155,8 +207,8 @@
       struct pf_position *b = &new_path->positions[i];
 
       if (a->dir_to_next_pos != b->dir_to_next_pos
-          || !same_pos(a->tile, b->tile)) {
-        break;
+         || !same_pos(a->tile, b->tile)) {
+       break;
       }
     }
     start_index = i;
@@ -195,7 +247,7 @@
   p->end_fuel_left = pf_last_position(p->path)->fuel_left;
 
   if (hover_state == HOVER_CONNECT) {
-    int move_rate = goto_map.template.move_rate;
+    int move_rate = goto_map->template.move_rate;
     int moves = pf_last_position(p->path)->total_MC;
 
     p->time = moves / move_rate;
@@ -203,8 +255,7 @@
       p->time += connect_initial;
     }
     freelog(PATH_LOG_LEVEL, "To (%d,%d) MC: %d, connect_initial: %d",
-            TILE_XY(ptile), moves, connect_initial);
-
+           TILE_XY(ptile), moves, connect_initial);
   } else {
     p->time = pf_last_position(p->path)->turn;
   }
@@ -218,13 +269,13 @@
   Change the drawn path to a size of 0 steps by setting it to the
   start position.
 ***********************************************************************/
-static void reset_last_part(void)
+static void reset_last_part(struct goto_map *goto_map)
 {
-  struct part *p = &goto_map.parts[goto_map.num_parts - 1];
+  struct part *p = &goto_map->parts[goto_map->num_parts - 1];
 
   if (!same_pos(p->start_tile, p->end_tile)) {
     /* Otherwise no need to update */
-    update_last_part(p->start_tile);
+    update_last_part(goto_map, p->start_tile);
   }
 }
 
@@ -233,26 +284,29 @@
   of the new part is either the unit position (for the first part) or
   the destination of the last part (not the first part).
 ***********************************************************************/
-static void add_part(void)
+static void add_part(struct goto_map *goto_map)
 {
   struct part *p;
-  struct pf_parameter parameter = goto_map.template;
+  struct pf_parameter parameter = goto_map->template;
+  struct unit *punit = find_unit_by_id(goto_map->unit_id);
 
-  goto_map.num_parts++;
-  goto_map.parts =
-      fc_realloc(goto_map.parts,
-                 goto_map.num_parts * sizeof(*goto_map.parts));
-  p = &goto_map.parts[goto_map.num_parts - 1];
+  if (!punit) {
+    return;
+  }
 
-  if (goto_map.num_parts == 1) {
+  goto_map->num_parts++;
+  goto_map->parts =
+      fc_realloc(goto_map->parts,
+                 goto_map->num_parts * sizeof(*goto_map->parts));
+  p = &goto_map->parts[goto_map->num_parts - 1];
+
+  if (goto_map->num_parts == 1) {
     /* first part */
-    struct unit *punit = find_unit_by_id(goto_map.unit_id);
-
     p->start_tile = punit->tile;
     p->start_moves_left = parameter.moves_left_initially;
     p->start_fuel_left = parameter.fuel_left_initially;
   } else {
-    struct part *prev = &goto_map.parts[goto_map.num_parts - 2];
+    struct part *prev = &goto_map->parts[goto_map->num_parts - 2];
 
     p->start_tile = prev->end_tile;
     p->start_moves_left = prev->end_moves_left;
@@ -270,19 +324,19 @@
 /********************************************************************** 
   Remove the last part, erasing the corresponding path segment.
 ***********************************************************************/
-static void remove_last_part(void)
+static void remove_last_part(struct goto_map *goto_map)
 {
-  struct part *p = &goto_map.parts[goto_map.num_parts - 1];
+  struct part *p = &goto_map->parts[goto_map->num_parts - 1];
 
-  assert(goto_map.num_parts >= 1);
+  assert(goto_map->num_parts >= 1);
 
-  reset_last_part();
+  reset_last_part(goto_map);
   if (p->path) {
     /* We do not always have a path */
     pf_destroy_path(p->path);
   }
   pf_destroy_map(p->map);
-  goto_map.num_parts--;
+  goto_map->num_parts--;
 }
 
 /********************************************************************** 
@@ -290,16 +344,15 @@
 ***********************************************************************/
 void goto_add_waypoint(void)
 {
-  struct part *p = &goto_map.parts[goto_map.num_parts - 1];
-
   assert(is_active);
-  assert(find_unit_by_id(goto_map.unit_id)
-        && find_unit_by_id(goto_map.unit_id) == get_unit_in_focus());
+  goto_map_iterate(goto_maps, goto_map, punit) {
+    struct part *p = &goto_map->parts[goto_map->num_parts - 1];
 
-  if (!same_pos(p->start_tile, p->end_tile)) {
-    /* Otherwise the last part has zero length. */
-    add_part();
-  }
+    if (!same_pos(p->start_tile, p->end_tile)) {
+      /* Otherwise the last part has zero length. */
+      add_part(goto_map);
+    }
+  } goto_map_iterate_end;
 }
 
 /********************************************************************** 
@@ -308,27 +361,29 @@
 ***********************************************************************/
 bool goto_pop_waypoint(void)
 {
-  struct part *p = &goto_map.parts[goto_map.num_parts - 1];
-  struct tile *end_tile = p->end_tile;
+  bool popped = FALSE;
 
   assert(is_active);
-  assert(find_unit_by_id(goto_map.unit_id)
-        && find_unit_by_id(goto_map.unit_id) == get_unit_in_focus());
+  goto_map_iterate(goto_maps, goto_map, punit) {
+    struct part *p = &goto_map->parts[goto_map->num_parts - 1];
+    struct tile *end_tile = p->end_tile;
 
-  if (goto_map.num_parts == 1) {
-    /* we don't have any waypoint but the start pos. */
-    return FALSE;
-  }
+    if (goto_map->num_parts == 1) {
+      /* we don't have any waypoint but the start pos. */
+      continue;
+    }
+    popped = TRUE;
 
-  remove_last_part();
+    remove_last_part(goto_map);
 
-  /* 
-   * Set the end position of the previous part (now the last) to the
-   * end position of the last part (now gone). I.e. redraw a line to
-   * the mouse position. 
-   */
-  update_last_part(end_tile);
-  return TRUE;
+    /* 
+     * Set the end position of the previous part (now the last) to the
+     * end position of the last part (now gone). I.e. redraw a line to
+     * the mouse position. 
+     */
+    update_last_part(goto_map, end_tile);
+  } goto_map_iterate_end;
+  return popped;
 }
 
 /********************************************************************** 
@@ -710,16 +765,23 @@
   Enter the goto state: activate, prepare PF-template and add the 
   initial part.
 ***********************************************************************/
-void enter_goto_state(struct unit *punit)
+void enter_goto_state(struct unit_list *punits)
 {
   assert(!is_active);
+  assert(goto_map_list_size(goto_maps) == 0);
 
-  goto_map.unit_id = punit->id;
-  assert(goto_map.num_parts == 0);
+  unit_list_iterate(punits, punit) {
+    struct goto_map *goto_map = goto_map_new();
 
-  fill_client_goto_parameter(punit, &goto_map.template);
+    goto_map->unit_id = punit->id;
+    assert(goto_map->num_parts == 0);
 
-  add_part();
+    fill_client_goto_parameter(punit, &goto_map->template);
+
+    add_part(goto_map);
+
+    goto_map_list_append(goto_maps, goto_map);
+  } unit_list_iterate_end;
   is_active = TRUE;
 }
 
@@ -732,11 +794,13 @@
     return;
   }
 
-  while (goto_map.num_parts > 0) {
-    remove_last_part();
-  }
-  free(goto_map.parts);
-  goto_map.parts = NULL;
+  goto_map_list_iterate(goto_maps, goto_map) {
+    while (goto_map->num_parts > 0) {
+      remove_last_part(goto_map);
+    }
+    goto_map_free(goto_map);
+  } goto_map_list_iterate_end;
+  goto_map_list_unlink_all(goto_maps);
 
   is_active = FALSE;
 }
@@ -754,11 +818,14 @@
 ***********************************************************************/
 struct tile *get_line_dest(void)
 {
-  struct part *p = &goto_map.parts[goto_map.num_parts - 1];
+  if (is_active) {
+    struct goto_map *goto_map = goto_map_list_get(goto_maps, 0);
+    struct part *p = &goto_map->parts[goto_map->num_parts - 1];
 
-  assert(is_active);
-
-  return p->end_tile;
+    return p->end_tile;
+  } else {
+    return NULL;
+  }
 }
 
 /**************************************************************************
@@ -766,13 +833,18 @@
 ***************************************************************************/
 int get_goto_turns(void)
 {
-  int time = 0, i;
+  if (is_active) {
+    struct goto_map *goto_map = goto_map_list_get(goto_maps, 0);
+    int time = 0, i;
 
-  for(i = 0; i < goto_map.num_parts; i++) {
-    time += goto_map.parts[i].time;
+    for (i = 0; i < goto_map->num_parts; i++) {
+      time += goto_map->parts[i].time;
+    }
+
+    return time;
+  } else {
+    return -1;
   }
-
-  return time;
 }
 
 /********************************************************************** 
@@ -784,10 +856,12 @@
 {
   assert(is_active);
 
-  update_last_part(dest_tile);
+  goto_map_iterate(goto_maps, goto_map, punit) {
+    update_last_part(goto_map, dest_tile);
+  } goto_map_iterate_end;
 
   /* Update goto data in info label. */
-  update_unit_info_label(get_unit_in_focus());
+  update_unit_info_label(get_units_in_focus());
 }
 
 /****************************************************************************
@@ -918,118 +992,118 @@
   Send the current patrol route (i.e., the one generated via HOVER_STATE)
   to the server.
 **************************************************************************/
-void send_patrol_route(struct unit *punit)
+void send_patrol_route(void)
 {
-  int i;
-  struct pf_path *path = NULL, *return_path;
-  struct pf_parameter parameter = goto_map.template;
-  struct pf_map *map;
-
   assert(is_active);
-  assert(punit->id == goto_map.unit_id);
+  goto_map_iterate(goto_maps, goto_map, punit) {
+    int i;
+    struct pf_path *path = NULL, *return_path;
+    struct pf_parameter parameter = goto_map->template;
+    struct pf_map *map;
 
-  parameter.start_tile = goto_map.parts[goto_map.num_parts - 1].end_tile;
-  parameter.moves_left_initially
-    = goto_map.parts[goto_map.num_parts - 1].end_moves_left;
-  parameter.fuel_left_initially
-    = goto_map.parts[goto_map.num_parts - 1].end_fuel_left;
-  map = pf_create_map(&parameter);
-  return_path = pf_get_path(map, goto_map.parts[0].start_tile);
-  if (!return_path) {
-    die("No return path found!");
-  }
+    parameter.start_tile = goto_map->parts[goto_map->num_parts - 1].end_tile;
+    parameter.moves_left_initially
+      = goto_map->parts[goto_map->num_parts - 1].end_moves_left;
+    parameter.fuel_left_initially
+      = goto_map->parts[goto_map->num_parts - 1].end_fuel_left;
+    map = pf_create_map(&parameter);
+    return_path = pf_get_path(map, goto_map->parts[0].start_tile);
+    if (!return_path) {
+      die("No return path found!");
+    }
 
-  for (i = 0; i < goto_map.num_parts; i++) {
-    path = pft_concat(path, goto_map.parts[i].path);
-  }
-  path = pft_concat(path, return_path);
+    for (i = 0; i < goto_map->num_parts; i++) {
+      path = pft_concat(path, goto_map->parts[i].path);
+    }
+    path = pft_concat(path, return_path);
 
-  pf_destroy_map(map);
-  pf_destroy_path(return_path);
+    pf_destroy_map(map);
+    pf_destroy_path(return_path);
 
-  send_path_orders(punit, path, TRUE, TRUE, NULL);
+    send_path_orders(punit, path, TRUE, TRUE, NULL);
 
-  pf_destroy_path(path);
+    pf_destroy_path(path);
+  } goto_map_iterate_end;
 }
 
 /**************************************************************************
   Send the current connect route (i.e., the one generated via HOVER_STATE)
   to the server.
 **************************************************************************/
-void send_connect_route(struct unit *punit, enum unit_activity activity)
+void send_connect_route(enum unit_activity activity)
 {
-  struct pf_path *path = NULL;
-  int i;
-  struct packet_unit_orders p;
-  struct tile *old_tile;
-
   assert(is_active);
-  assert(punit->id == goto_map.unit_id);
+  goto_map_iterate(goto_maps, goto_map, punit) {
+    struct pf_path *path = NULL;
+    int i;
+    struct packet_unit_orders p;
+    struct tile *old_tile;
 
-  memset(&p, 0, sizeof(p));
+    memset(&p, 0, sizeof(p));
 
-  for (i = 0; i < goto_map.num_parts; i++) {
-    path = pft_concat(path, goto_map.parts[i].path);
-  }
+    for (i = 0; i < goto_map->num_parts; i++) {
+      path = pft_concat(path, goto_map->parts[i].path);
+    }
 
-  p.unit_id = punit->id;
-  p.src_x = punit->tile->x;
-  p.src_y = punit->tile->y;
-  p.repeat = FALSE;
-  p.vigilant = FALSE; /* Should be TRUE? */
+    p.unit_id = punit->id;
+    p.src_x = punit->tile->x;
+    p.src_y = punit->tile->y;
+    p.repeat = FALSE;
+    p.vigilant = FALSE; /* Should be TRUE? */
 
-  p.length = 0;
-  old_tile = path->positions[0].tile;
+    p.length = 0;
+    old_tile = path->positions[0].tile;
 
-  for (i = 0; i < path->length; i++) {
-    switch (activity) {
-    case ACTIVITY_IRRIGATE:
-      if (!tile_has_special(old_tile, S_IRRIGATION)) {
-       /* Assume the unit can irrigate or we wouldn't be here. */
-       p.orders[p.length] = ORDER_ACTIVITY;
-       p.activity[p.length] = ACTIVITY_IRRIGATE;
-       p.length++;
-      }
-      break;
-    case ACTIVITY_ROAD:
-    case ACTIVITY_RAILROAD:
-      if (!tile_has_special(old_tile, S_ROAD)) {
-       /* Assume the unit can build the road or we wouldn't be here. */
-       p.orders[p.length] = ORDER_ACTIVITY;
-       p.activity[p.length] = ACTIVITY_ROAD;
-       p.length++;
-      }
-      if (activity == ACTIVITY_RAILROAD) {
-       if (!tile_has_special(old_tile, S_RAILROAD)) {
-         /* Assume the unit can build the rail or we wouldn't be here. */
+    for (i = 0; i < path->length; i++) {
+      switch (activity) {
+      case ACTIVITY_IRRIGATE:
+       if (!tile_has_special(old_tile, S_IRRIGATION)) {
+         /* Assume the unit can irrigate or we wouldn't be here. */
          p.orders[p.length] = ORDER_ACTIVITY;
-         p.activity[p.length] = ACTIVITY_RAILROAD;
+         p.activity[p.length] = ACTIVITY_IRRIGATE;
          p.length++;
        }
+       break;
+      case ACTIVITY_ROAD:
+      case ACTIVITY_RAILROAD:
+       if (!tile_has_special(old_tile, S_ROAD)) {
+         /* Assume the unit can build the road or we wouldn't be here. */
+         p.orders[p.length] = ORDER_ACTIVITY;
+         p.activity[p.length] = ACTIVITY_ROAD;
+         p.length++;
+       }
+       if (activity == ACTIVITY_RAILROAD) {
+         if (!tile_has_special(old_tile, S_RAILROAD)) {
+           /* Assume the unit can build the rail or we wouldn't be here. */
+           p.orders[p.length] = ORDER_ACTIVITY;
+           p.activity[p.length] = ACTIVITY_RAILROAD;
+           p.length++;
+         }
+       }
+       break;
+      default:
+       die("Invalid connect activity.");
+       break;
       }
-      break;
-    default:
-      die("Invalid connect activity.");
-      break;
-    }
 
-    if (i != path->length - 1) {
-      struct tile *new_tile = path->positions[i + 1].tile;
+      if (i != path->length - 1) {
+       struct tile *new_tile = path->positions[i + 1].tile;
 
-      assert(!same_pos(new_tile, old_tile));
+       assert(!same_pos(new_tile, old_tile));
 
-      p.orders[p.length] = ORDER_MOVE;
-      p.dir[p.length] = get_direction_for_step(old_tile, new_tile);
-      p.length++;
+       p.orders[p.length] = ORDER_MOVE;
+       p.dir[p.length] = get_direction_for_step(old_tile, new_tile);
+       p.length++;
 
-      old_tile = new_tile;
+       old_tile = new_tile;
+      }
     }
-  }
 
-  p.dest_x = old_tile->x;
-  p.dest_y = old_tile->y;
+    p.dest_x = old_tile->x;
+    p.dest_y = old_tile->y;
 
-  send_packet_unit_orders(&aconnection, &p);
+    send_packet_unit_orders(&aconnection, &p);
+  } goto_map_iterate_end;
 }
 
 /**************************************************************************
@@ -1037,27 +1111,27 @@
   HOVER_STATE) to the server.  The route might involve more than one
   part if waypoints were used.  FIXME: danger paths are not supported.
 **************************************************************************/
-void send_goto_route(struct unit *punit)
+void send_goto_route(void)
 {
-  struct pf_path *path = NULL;
-  int i;
-
   assert(is_active);
-  assert(punit->id == goto_map.unit_id);
+  goto_map_iterate(goto_maps, goto_map, punit) {
+    struct pf_path *path = NULL;
+    int i;
 
-  for (i = 0; i < goto_map.num_parts; i++) {
-    path = pft_concat(path, goto_map.parts[i].path);
-  }
+    for (i = 0; i < goto_map->num_parts; i++) {
+      path = pft_concat(path, goto_map->parts[i].path);
+    }
 
-  if (goto_last_order == ORDER_LAST) {
-    send_goto_path(punit, path, NULL);
-  } else {
-    struct unit_order order;
+    if (goto_last_order == ORDER_LAST) {
+      send_goto_path(punit, path, NULL);
+    } else {
+      struct unit_order order;
 
-    order.order = goto_last_order;
-    send_goto_path(punit, path, &order);
-  }
-  pf_destroy_path(path);
+      order.order = goto_last_order;
+      send_goto_path(punit, path, &order);
+    }
+    pf_destroy_path(path);
+  } goto_map_iterate_end;
 }
 
 /* ================= drawn functions ============================ */
Index: client/packhand.c
===================================================================
--- client/packhand.c   (revision 11150)
+++ client/packhand.c   (working copy)
@@ -115,6 +115,7 @@
   } else {
     punit->transported_by = -1;
   }
+  punit->battlegroup = packet->battlegroup;
   punit->has_orders = packet->has_orders;
   punit->orders.length = packet->orders_length;
   punit->orders.index = packet->orders_index;
@@ -227,7 +228,7 @@
   client_remove_city(pcity);
 
   /* update menus if the focus unit is on the tile. */
-  if (get_unit_in_focus()) {
+  if (get_num_units_in_focus() > 0) {
     update_menus();
   }
 }
@@ -360,7 +361,7 @@
 
     update_info_label();       /* get initial population right */
     update_unit_focus();
-    update_unit_info_label(get_unit_in_focus());
+    update_unit_info_label(get_units_in_focus());
 
     /* Find something sensible to display instead of the intro gfx. */
     center_on_something();
@@ -392,7 +393,7 @@
   bool need_units_dialog_update = FALSE;
   struct city *pcity;
   bool popup, update_descriptions = FALSE, name_changed = FALSE;
-  struct unit *pfocus_unit = get_unit_in_focus();
+  struct unit_list *pfocus_units = get_units_in_focus();
 
   pcity=find_city_by_id(packet->id);
 
@@ -554,8 +555,13 @@
   }
 
   /* Update focus unit info label if necessary. */
-  if (name_changed && pfocus_unit && pfocus_unit->homecity == pcity->id) {
-    update_unit_info_label(pfocus_unit);
+  if (name_changed) {
+    unit_list_iterate(pfocus_units, pfocus_unit) {
+      if (pfocus_unit->homecity == pcity->id) {
+       update_unit_info_label(pfocus_units);
+       break;
+      }
+    } unit_list_iterate_end;
   }
 
   /* Update the units dialog if necessary. */
@@ -622,11 +628,8 @@
   }
 
   /* update menus if the focus unit is on the tile. */
-  {
-    struct unit *punit = get_unit_in_focus();
-    if (punit && same_pos(punit->tile, pcity->tile)) {
-      update_menus();
-    }
+  if (get_focus_unit_on_tile(pcity->tile)) {
+    update_menus();
   }
 
   if(is_new) {
@@ -775,7 +778,7 @@
   update_unit_focus();
   auto_center_on_focus_unit();
 
-  update_unit_info_label(get_unit_in_focus());
+  update_unit_info_label(get_units_in_focus());
   update_menus();
 
   set_seconds_to_turndone(game.info.timeout);
@@ -961,7 +964,6 @@
   bool check_focus = FALSE;     /* conservative focus change */
   bool moved = FALSE;
   bool ret = FALSE;
-  struct unit *focus_unit = get_unit_in_focus();
   
   punit = player_find_unit_by_id(packet_unit->owner, packet_unit->id);
   if (!punit && find_unit_by_id(packet_unit->id)) {
@@ -973,12 +975,25 @@
   if (punit) {
     ret = TRUE;
     punit->activity_count = packet_unit->activity_count;
+    if (packet_unit->battlegroup < 0
+       || packet_unit->battlegroup >= MAX_NUM_BATTLEGROUPS) {
+      packet_unit->battlegroup = BATTLEGROUP_NONE;
+    }
+    if (punit->battlegroup != packet_unit->battlegroup) {
+      if (packet_unit->battlegroup != BATTLEGROUP_NONE) {
+       unit_list_append(battlegroups[packet_unit->battlegroup], punit);
+      }
+      if (punit->battlegroup != BATTLEGROUP_NONE) {
+       unit_list_unlink(battlegroups[punit->battlegroup], punit);
+      }
+      punit->battlegroup = packet_unit->battlegroup;
+    }
     if (punit->ai.control != packet_unit->ai.control) {
       punit->ai.control = packet_unit->ai.control;
       repaint_unit = TRUE;
       /* AI is set:     may change focus */
       /* AI is cleared: keep focus */
-      if (packet_unit->ai.control && punit == get_unit_in_focus()) {
+      if (packet_unit->ai.control && unit_is_in_focus(punit)) {
         check_focus = TRUE;
       }
     }
@@ -997,7 +1012,7 @@
       /* May change focus if focus unit gets a new activity.
        * But if new activity is Idle, it means user specifically selected
        * the unit */
-      if (punit == get_unit_in_focus()
+      if (unit_is_in_focus(punit)
          && (packet_unit->activity != ACTIVITY_IDLE
              || packet_unit->has_orders)) {
         check_focus = TRUE;
@@ -1013,9 +1028,8 @@
           && punit->activity == ACTIVITY_SENTRY
           && packet_unit->activity == ACTIVITY_IDLE
          && is_player_phase(game.player_ptr, game.info.phase)
-          && (!get_unit_in_focus()
               /* only 1 wakeup focus per tile is useful */
-              || !same_pos(packet_unit->tile, get_unit_in_focus()->tile))) {
+          && !get_focus_unit_on_tile(packet_unit->tile)) {
         set_unit_focus(punit);
         check_focus = FALSE; /* and keep it */
 
@@ -1031,7 +1045,7 @@
 
       punit->transported_by = packet_unit->transported_by;
       if (punit->occupy != packet_unit->occupy
-         && focus_unit && focus_unit->tile == packet_unit->tile) {
+         && get_focus_unit_on_tile(packet_unit->tile)) {
        /* Special case: (un)loading a unit in a transporter on the
         * same tile as the focus unit may (dis)allow the focus unit to be
         * loaded.  Thus the orders->(un)load menu item needs updating. */
@@ -1059,7 +1073,7 @@
 
     /* These two lines force the menus to be updated as appropriate when
      * the focus unit changes. */
-    if (punit == get_unit_in_focus()) {
+    if (unit_is_in_focus(punit)) {
       need_update_menus = TRUE;
     }
 
@@ -1094,7 +1108,7 @@
       if (pcity && (pcity->id != punit->homecity)) {
        refresh_city_dialog(pcity);
       }
-      if(punit == get_unit_in_focus()) {
+      if (unit_is_in_focus(punit)) {
         /* Update the orders menu -- the unit might have new abilities */
        need_update_menus = TRUE;
       }
@@ -1102,7 +1116,7 @@
 
     /* May change focus if an attempted move or attack exhausted unit */
     if (punit->moves_left != packet_unit->moves_left
-        && punit == get_unit_in_focus()) {
+        && unit_is_in_focus(punit)) {
       check_focus = TRUE;
     }
 
@@ -1206,6 +1220,11 @@
     unit_list_prepend(punit->owner->units, punit);
     unit_list_prepend(punit->tile->units, punit);
 
+    if (punit->battlegroup >= 0
+       && punit->battlegroup < MAX_NUM_BATTLEGROUPS) {
+      unit_list_append(battlegroups[punit->battlegroup], punit);
+    }
+
     if((pcity=find_city_by_id(punit->homecity))) {
       unit_list_prepend(pcity->units_supported, punit);
     }
@@ -1226,20 +1245,17 @@
 
   assert(punit != NULL);
 
-  if (punit == get_unit_in_focus()) {
-    update_unit_info_label(punit);
-  } else if (get_unit_in_focus()
-            && (same_pos(get_unit_in_focus()->tile, punit->tile)
-                || (moved
-                    && same_pos(get_unit_in_focus()->tile, old_tile)))) {
-    update_unit_info_label(get_unit_in_focus());
+  if (unit_is_in_focus(punit)
+      || get_focus_unit_on_tile(punit->tile)
+      || (moved && get_focus_unit_on_tile(old_tile))) {
+    update_unit_info_label(get_units_in_focus());
   }
 
   if (repaint_unit) {
     refresh_unit_mapcanvas(punit, punit->tile, TRUE, FALSE);
   }
 
-  if ((check_focus || get_unit_in_focus() == NULL)
+  if ((check_focus || get_num_units_in_focus() == 0)
       && game.player_ptr
       && !game.player_ptr->ai.control
       && is_player_phase(game.player_ptr, game.info.phase)) {
@@ -1529,7 +1545,7 @@
       /* If we just learned bridge building and focus is on a settler
         on a river the road menu item will remain disabled unless we
         do this. (applys in other cases as well.) */
-      if (get_unit_in_focus()) {
+      if (get_num_units_in_focus() > 0) {
        update_menus();
       }
     }
@@ -2014,8 +2030,7 @@
 
   /* update menus if the focus unit is on the tile. */
   if (tile_changed) {
-    struct unit *punit = get_unit_in_focus();
-    if (punit && same_pos(punit->tile, ptile)) {
+    if (get_focus_unit_on_tile(ptile)) {
       update_menus();
     }
   }
Index: client/goto.h
===================================================================
--- client/goto.h       (revision 11150)
+++ client/goto.h       (working copy)
@@ -19,7 +19,7 @@
 
 void init_client_goto(void);
 void free_client_goto(void);
-void enter_goto_state(struct unit *punit);
+void enter_goto_state(struct unit_list *punits);
 void exit_goto_state(void);
 bool goto_is_active(void);
 struct tile *get_line_dest(void);
@@ -36,9 +36,9 @@
 void send_goto_path(struct unit *punit, struct pf_path *path,
                    struct unit_order *last_order);
 bool send_goto_tile(struct unit *punit, struct tile *ptile);
-void send_patrol_route(struct unit *punit);
-void send_goto_route(struct unit *punit);
-void send_connect_route(struct unit *punit, enum unit_activity activity);
+void send_patrol_route(void);
+void send_goto_route(void);
+void send_connect_route(enum unit_activity activity);
 
 struct pf_path *path_to_nearest_allied_city(struct unit *punit);
 
Index: client/tilespec.c
===================================================================
--- client/tilespec.c   (revision 11150)
+++ client/tilespec.c   (working copy)
@@ -190,6 +190,7 @@
       *transform,
       *connect,
       *patrol,
+      *battlegroup[MAX_NUM_BATTLEGROUPS],
       *lowfuel,
       *tired;
   } unit;
@@ -1861,7 +1862,7 @@
 ***********************************************************************/
 static void tileset_lookup_sprite_tags(struct tileset *t)
 {
-  char buffer[512];
+  char buffer[512], buffer2[512];
   const char dir_char[] = "nsew";
   const int W = t->normal_tile_width, H = t->normal_tile_height;
   int i, j;
@@ -2040,6 +2041,12 @@
   SET_SPRITE(unit.transform,    "unit.transform");
   SET_SPRITE(unit.connect,      "unit.connect");
   SET_SPRITE(unit.patrol,       "unit.patrol");
+  for (i = 0; i < MAX_NUM_BATTLEGROUPS; i++) {
+    my_snprintf(buffer, sizeof(buffer), "unit.battlegroup_%d", i);
+    my_snprintf(buffer2, sizeof(buffer2), "city.size_%d", i + 1);
+    assert(MAX_NUM_BATTLEGROUPS < NUM_TILES_DIGITS);
+    SET_SPRITE_ALT(unit.battlegroup[i], buffer, buffer2);
+  }
   SET_SPRITE(unit.lowfuel, "unit.lowfuel");
   SET_SPRITE(unit.tired, "unit.tired");
 
@@ -2086,8 +2093,6 @@
   SET_SPRITE(city.disorder, "city.disorder");
 
   for(i=0; i<NUM_TILES_DIGITS; i++) {
-    char buffer2[512];
-
     my_snprintf(buffer, sizeof(buffer), "city.size_%d", i);
     SET_SPRITE(city.size[i], buffer);
     my_snprintf(buffer2, sizeof(buffer2), "path.turns_%d", i);
@@ -2926,6 +2931,10 @@
     }
   }
 
+  if (punit->battlegroup != BATTLEGROUP_NONE) {
+    ADD_SPRITE_FULL(t->sprites.unit.battlegroup[punit->battlegroup]);
+  }
+
   if (t->sprites.unit.lowfuel
       && unit_type(punit)->fuel > 0
       && punit->fuel == 1
@@ -3660,7 +3669,7 @@
     bool known[NUM_EDGE_TILES], city[NUM_EDGE_TILES];
     bool unit[NUM_EDGE_TILES], worked[NUM_EDGE_TILES];
     int i;
-    struct unit *pfocus = get_unit_in_focus();
+    struct unit_list *pfocus_units = get_units_in_focus();
 
     for (i = 0; i < NUM_EDGE_TILES; i++) {
       const struct tile *tile = pedge->tile[i];
@@ -3668,9 +3677,18 @@
       int dummy_x, dummy_y;
 
       known[i] = tile && client_tile_get_known(tile) != TILE_UNKNOWN;
-      unit[i] = tile && pfocus && unit_flag(pfocus, F_CITIES)
-       && city_can_be_built_here(pfocus->tile, pfocus)
-       && base_map_to_city_map(&dummy_x, &dummy_y, pfocus->tile, tile);
+      unit[i] = FALSE;
+      if (tile) {
+       unit_list_iterate(pfocus_units, pfocus_unit) {
+         if (unit_flag(pfocus_unit, F_CITIES)
+             && city_can_be_built_here(pfocus_unit->tile, pfocus_unit)
+             && base_map_to_city_map(&dummy_x, &dummy_y,
+                                     pfocus_unit->tile, tile)) {
+           unit[i] = TRUE;
+           break;
+         }
+       } unit_list_iterate_end;
+      }
       worked[i] = FALSE;
 
       city[i] = (tile
@@ -3830,14 +3848,14 @@
   struct terrain *pterrain = NULL, *tterrain_near[8];
   bv_special tspecial, tspecial_near[8];
   int tileno, dir;
-  struct unit *pfocus = get_unit_in_focus();
   struct drawn_sprite *save_sprs = sprs;
   struct player *owner = NULL;
 
   /* Unit drawing is disabled if the view options is turned off, but only
    * if we're drawing on the mapview. */
   bool do_draw_unit = (punit && (draw_units || !ptile
-                                || (draw_focus_unit && pfocus == punit)));
+                                || (draw_focus_unit
+                                    && unit_is_in_focus(punit))));
   bool solid_bg = (solid_color_behind_units
                   && (do_draw_unit
                       || (pcity && draw_cities)
@@ -4030,12 +4048,11 @@
 
   case LAYER_UNIT:
   case LAYER_FOCUS_UNIT:
-    if (do_draw_unit && XOR(layer == LAYER_UNIT,
-                           punit == get_unit_in_focus())) {
+    if (do_draw_unit && XOR(layer == LAYER_UNIT, unit_is_in_focus(punit))) {
       bool stacked = ptile && (unit_list_size(ptile->units) > 1);
       bool backdrop = !pcity;
 
-      if (ptile && punit == get_unit_in_focus()
+      if (ptile && unit_is_in_focus(punit)
          && t->sprites.unit.select[0]) {
        /* Special case for drawing the selection rectangle.  The blinking
         * unit is handled separately, inside get_drawable_unit(). */
@@ -4279,7 +4296,6 @@
                               const struct city *citymode)
 {
   struct unit *punit = find_visible_unit(ptile);
-  struct unit *pfocus = get_unit_in_focus();
 
   if (!punit)
     return NULL;
@@ -4287,9 +4303,8 @@
   if (citymode && punit->owner == citymode->owner)
     return NULL;
 
-  if (punit != pfocus
-      || t->sprites.unit.select[0] || focus_unit_state == 0
-      || !same_pos(punit->tile, pfocus->tile))
+  if (!unit_is_in_focus(punit)
+      || t->sprites.unit.select[0] || focus_unit_state == 0)
     return punit;
   else
     return NULL;
Index: client/mapctrl_common.c
===================================================================
--- client/mapctrl_common.c     (revision 11150)
+++ client/mapctrl_common.c     (working copy)
@@ -427,12 +427,9 @@
   struct tile *ptile = canvas_pos_to_tile(canvas_x, canvas_y);
 
   if (keyboardless_goto_active && hover_state == HOVER_GOTO && ptile) {
-    struct unit *punit =
-        player_find_unit_by_id(game.player_ptr, hover_unit);
-
     do_unit_goto(ptile);
     set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
-    update_unit_info_label(punit);
+    update_unit_info_label(hover_units);
   }
   keyboardless_goto_active = FALSE;
   keyboardless_goto_button_down = FALSE;
@@ -447,7 +444,7 @@
 {
   struct tile *ptile = canvas_pos_to_tile(canvas_x, canvas_y);
 
-  if (ptile && get_unit_in_focus()
+  if (ptile && get_num_units_in_focus() > 0
       && !same_pos(keyboardless_goto_start_tile, ptile)
       && can_client_issue_orders()) {
     keyboardless_goto_active = TRUE;
@@ -658,7 +655,7 @@
 {
   struct unit *my_unit, *defender, *attacker;
 
-  if (!(my_unit = get_unit_in_focus())
+  if (!unit_is_in_focus(my_unit)
       || !(defender = get_defender(my_unit, ptile))
       || !(attacker = get_attacker(my_unit, ptile))) {
     return FALSE;
Index: client/civclient.c
===================================================================
--- client/civclient.c  (revision 11150)
+++ client/civclient.c  (working copy)
@@ -680,7 +680,7 @@
     time_until_next_call = MIN(time_until_next_call, blink_time);
   }
 
-  if (get_unit_in_focus()) {
+  if (get_num_units_in_focus() > 0) {
     double blink_time = blink_active_unit();
 
     time_until_next_call = MIN(time_until_next_call, blink_time);
Index: client/mapview_common.c
===================================================================
--- client/mapview_common.c     (revision 11150)
+++ client/mapview_common.c     (working copy)
@@ -1733,9 +1733,9 @@
     return;
   }
 
-  if (punit == get_unit_in_focus() && hover_state != HOVER_NONE) {
+  if (unit_is_in_focus(punit) && hover_state != HOVER_NONE) {
     set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
-    update_unit_info_label(punit);
+    update_unit_info_label(get_units_in_focus());
   }
 
   dest_x = src_tile->x + dx;
Index: client/climisc.c
===================================================================
--- client/climisc.c    (revision 11150)
+++ client/climisc.c    (working copy)
@@ -74,28 +74,28 @@
   struct city *pcity;
   struct tile *ptile = punit->tile;
   int hc = punit->homecity;
-  struct unit *ufocus = get_unit_in_focus();
   struct unit old_unit = *punit;
+  int old = get_num_units_in_focus();
+  bool update;
+  int i;
 
   freelog(LOG_DEBUG, "removing unit %d, %s %s (%d %d) hcity %d",
          punit->id, get_nation_name(unit_owner(punit)->nation),
          unit_name(punit->type), TILE_XY(punit->tile), hc);
 
-  if (punit == ufocus) {
-    set_unit_focus(NULL);
-    game_remove_unit(punit);
-    punit = ufocus = NULL;
+  update = (get_first_unit_in_focus()
+           && same_pos(get_first_unit_in_focus()->tile, punit->tile));
+  unit_list_unlink(get_units_in_focus(), punit);
+  unit_list_unlink(hover_units, punit);
+  for (i = 0; i < MAX_NUM_BATTLEGROUPS; i++) {
+    unit_list_unlink(battlegroups[i], punit);
+  }
+  game_remove_unit(punit);
+  punit = NULL;
+  if (old > 0 && get_num_units_in_focus() == 0) {
     advance_unit_focus();
-  } else {
-    /* calculate before punit disappears, use after punit removed: */
-    bool update = (ufocus
-                  && same_pos(ufocus->tile, punit->tile));
-
-    game_remove_unit(punit);
-    punit = NULL;
-    if (update) {
-      update_unit_pix_label(ufocus);
-    }
+  } else if (update) {
+    update_unit_pix_label(get_units_in_focus());
   }
 
   pcity = tile_get_city(ptile);
@@ -409,7 +409,7 @@
   }
 
   can_slide = FALSE;
-  if ((punit = get_unit_in_focus())) {
+  if ((punit = get_first_unit_in_focus())) {
     center_tile_mapcanvas(punit->tile);
   } else if (game.player_ptr && (pcity = find_palace(game.player_ptr))) {
     /* Else focus on the capital. */

[Prev in Thread] Current Thread [Next in Thread]
  • [Freeciv-Dev] (PR#14365) battlegroups..., Jason Short <=