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

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

[Top] [All Lists]

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

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

Mateusz Stefek wrote:
> <URL: http://bugs.freeciv.org/Ticket/Display.html?id=14365 >
> 
> - Units seem to loose information about their groups.

Aside from the savegame problem, I don't see how.

> - I can't select whole group with number

Just press '1' and you select group 1...

> - In old savegames all units are in the second group.

Oops...looks like I used secfile_lookup_bool by default.

> I'm using the second patch.

This new patch fixes several focus-changing bugs.  I also added several 
new behaviors:

   shift-1: append group 1 to the focus
   ctrl-shift-1: append the focus to group 1

These are a hack since I don't know how to isolate the shift keypress on 
key 1 (it comes out as ! instead, and this is probably not portable).

-jason

Index: server/unittools.c
===================================================================
--- server/unittools.c  (revision 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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_int_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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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,11 +239,42 @@
                           TRUE, FALSE);
   }
 
-  update_unit_info_label(punit);
+  update_unit_info_label(pfocus_units);
   update_menus();
 }
 
 /**************************************************************************
+  Adds this unit to the list of units in focus.
+**************************************************************************/
+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;
+  }
+  if (unit_is_in_focus(punit)) {
+    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".
 **************************************************************************/
 void set_unit_focus_and_select(struct unit *punit)
@@ -212,6 +291,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 +309,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 +373,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()) {
@@ -265,10 +382,18 @@
   }
 
   set_hover_state(NULL, HOVER_NONE, ACTIVITY_LAST, ORDER_LAST);
-  if (!can_client_change_view()) {
-    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;
+      break;
+    }
+  } unit_list_iterate_end;
+
   if(!candidate) {
     /* First try for "waiting" units. */
     unit_list_iterate(game.player_ptr->units, punit) {
@@ -287,20 +412,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 +432,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 +441,28 @@
     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->owner == game.player_ptr
+         && 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 +471,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 +493,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 +535,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 +608,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 +660,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 +786,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 +872,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());
@@ -857,6 +970,25 @@
   wakeup_sentried_units(punit->tile);
 }
 
+/****************************************************************************
+  Select all units of the same type as the given unit.
+****************************************************************************/
+void request_unit_select_same_type(struct unit_list *punits)
+{
+  if (can_client_change_view()) {
+    unit_list_iterate(punits, punit) {
+      unit_list_iterate(unit_owner(punit)->units, punit2) {
+       if (punit2->type == punit->type
+           && !unit_list_search(punits, punit2)
+           && punit2->activity == ACTIVITY_IDLE
+           && !unit_has_orders(punit2)) {
+         add_unit_focus(punit2);
+       }
+      } unit_list_iterate_end;
+    } unit_list_iterate_end;
+  }
+}
+
 /**************************************************************************
   Request a diplomat to do a specific action.
   - action : The action to be requested.
@@ -1065,36 +1197,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 +1257,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,20 +1563,23 @@
 **************************************************************************/
 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);
   }
 }
 
 /**************************************************************************
 ...
 **************************************************************************/
-void request_unit_wait(struct unit *punit)
+void request_units_wait(struct unit_list *punits)
 {
-  punit->focus_status=FOCUS_WAIT;
-  if (punit == punit_focus) {
+  unit_list_iterate(punits, punit) {
+    punit->focus_status = FOCUS_WAIT;
+  } unit_list_iterate_end;
+  if (punits == get_units_in_focus()) {
     advance_unit_focus();
-    /* set_unit_focus(punit_focus); */  /* done in advance_unit_focus */
   }
 }
 
@@ -1429,10 +1588,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 +1664,9 @@
     update_city_description(dst_tile->city);
   }
 
-  if (punit_focus == punit) update_menus();
+  if (unit_is_in_focus(punit)) {
+    update_menus();
+  }
 }
 
 /**************************************************************************
@@ -1513,10 +1675,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 +1688,44 @@
       do_unit_goto(ptile);
       break;
     case HOVER_NUKE:
-      if (SINGLE_MOVE * 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 +1737,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 +1747,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 +1810,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 +1898,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 +1935,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 +1955,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 +1986,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 +2038,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 +2050,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 +2060,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 +2072,7 @@
 **************************************************************************/
 void key_unit_connect(enum unit_activity activity)
 {
-  if (punit_focus) {
-    request_unit_connect(activity);
-  }
+  request_unit_connect(activity);
 }
 
 /**************************************************************************
@@ -1910,13 +2081,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 +2099,7 @@
 **************************************************************************/
 void key_unit_done(void)
 {
-  if (punit_focus) {
-    request_unit_move_done();
-  }
+  request_unit_move_done();
 }
 
 /**************************************************************************
@@ -1934,9 +2107,7 @@
 **************************************************************************/
 void key_unit_goto(void)
 {
-  if (punit_focus) {
-    request_unit_goto(ORDER_LAST);
-  }
+  request_unit_goto(ORDER_LAST);
 }
 
 /**************************************************************************
@@ -1944,9 +2115,7 @@
 **************************************************************************/
 void key_unit_nuke(void)
 {
-  if (punit_focus) {
-    request_unit_nuke(punit_focus);
-  }
+  request_unit_nuke(get_units_in_focus());
 }
 
 /**************************************************************************
@@ -1954,9 +2123,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 +2131,7 @@
 **************************************************************************/
 void key_unit_patrol(void)
 {
-  if (punit_focus) {
-    request_unit_patrol();
-  }
+  request_unit_patrol();
 }
 
 /**************************************************************************
@@ -1974,9 +2139,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 +2151,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 +2161,7 @@
 **************************************************************************/
 void key_unit_wait(void)
 {
-  if (punit_focus) {
-    request_unit_wait(punit_focus);
-  }
+  request_units_wait(get_units_in_focus());
 }
 
 /**************************************************************************
@@ -2004,9 +2169,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 +2179,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 +2191,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 +2204,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 +2216,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 +2226,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 +2238,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 +2250,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 +2262,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 +2272,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 +2284,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 +2296,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 +2308,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 +2320,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 +2334,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 +2346,75 @@
 **************************************************************************/
 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, bool append)
+{
+  if (game.player_ptr && can_client_issue_orders()
+      && battlegroups >= 0 && battlegroup < MAX_NUM_BATTLEGROUPS) {
+    if (!append) {
+      /* 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 != battlegroup) {
+       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);
+       refresh_unit_mapcanvas(punit, punit->tile, TRUE, FALSE);
+      }
+    } unit_list_iterate_end;
+    unit_list_iterate(battlegroups[battlegroup], punit) {
+      add_unit_focus(punit);
+    } unit_list_iterate_end;
   }
 }
 
+/****************************************************************************
+  Bring this battlegroup into focus.
+****************************************************************************/
+void key_unit_select_battlegroup(int battlegroup, bool append)
+{
+  if (game.player_ptr && can_client_change_view()
+      && battlegroups >= 0 && battlegroup < MAX_NUM_BATTLEGROUPS) {
+    int i = 0;
+
+    if (unit_list_size(battlegroups[battlegroup]) == 0 && !append) {
+      set_unit_focus(NULL);
+      return;
+    }
+
+    /* FIXME: this is very inefficient and can be improved. */
+    unit_list_iterate(battlegroups[battlegroup], punit) {
+      if (i == 0 && !append) {
+       set_unit_focus(punit);
+      } else {
+       add_unit_focus(punit);
+      }
+      i++;
+    } unit_list_iterate_end;
+  }
+}
+
 /**************************************************************************
   Toggle drawing of city outlines.
 **************************************************************************/
@@ -2324,10 +2565,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 11155)
+++ 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 11155)
+++ 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 11155)
+++ client/gui-gtk-2.0/gui_main.c       (working copy)
@@ -440,7 +440,57 @@
       }
     }
 
+    assert(MAX_NUM_BATTLEGROUPS == 4);
+#define BATTLEGROUP_CASE(num)                                              \
+  if (ev->state & GDK_CONTROL_MASK) {                                      \
+    key_unit_assign_battlegroup((num), (ev->state & GDK_SHIFT_MASK) != 0);  \
+  } else {                                                                 \
+    key_unit_select_battlegroup((num), (ev->state & GDK_SHIFT_MASK) != 0);  \
+  }
+#define BATTLEGROUP_SHIFT_CASE(num)                                        \
+  if (ev->state & GDK_CONTROL_MASK) {                                      \
+    key_unit_assign_battlegroup((num), TRUE);                              \
+  } else {                                                                 \
+    key_unit_select_battlegroup((num), TRUE);                              \
+  }
+    
     switch (ev->keyval) {
+    case GDK_1:
+      BATTLEGROUP_CASE(0);
+      break;
+
+    case GDK_2:
+      BATTLEGROUP_CASE(1);
+      break;
+
+    case GDK_3:
+      BATTLEGROUP_CASE(2);
+      break;
+
+    case GDK_4:
+      BATTLEGROUP_CASE(3);
+      break;
+
+    case GDK_exclam:
+      /* Shift + 1 */
+      BATTLEGROUP_SHIFT_CASE(0);
+      break;
+
+    case GDK_at:
+      /* Shift + 2 */
+      BATTLEGROUP_SHIFT_CASE(1);
+      break;
+
+    case GDK_numbersign:
+      /* Shift + 3 */
+      BATTLEGROUP_SHIFT_CASE(2);
+      break;
+
+    case GDK_dollar:
+      /* Shift + 4 */
+      BATTLEGROUP_SHIFT_CASE(3);
+      break;
+
       case GDK_KP_Up:
       case GDK_8:
       case GDK_KP_8:
@@ -460,25 +510,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 +737,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 +1534,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 11155)
+++ client/gui-gtk-2.0/menu.c   (working copy)
@@ -143,6 +143,7 @@
   MENU_ORDER_DISBAND,
   MENU_ORDER_DIPLOMAT_DLG,
   MENU_ORDER_NUKE,
+  MENU_ORDER_SELECT_SAME_TYPE,
   MENU_ORDER_WAIT,
   MENU_ORDER_DONE,
 
@@ -395,25 +396,31 @@
                                 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_SELECT_SAME_TYPE:
+    request_unit_select_same_type(get_units_in_focus());
+    break;
+  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 +431,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 +470,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 +508,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;
@@ -853,6 +871,8 @@
        orders_menu_callback,   MENU_ORDER_NUKE                                 
        },
   { "/" N_("Orders") "/sep5",                          NULL,
        NULL,                   0,                                      
"<Separator>"   },
+  { "/" N_("Orders") "/" N_("Select same type"), "y",
+    orders_menu_callback, MENU_ORDER_SELECT_SAME_TYPE },
   { "/" N_("Orders") "/" N_("_Wait"),                  "w",
        orders_menu_callback,   MENU_ORDER_WAIT                                 
        },
   { "/" N_("Orders") "/" N_("Done"),                   "space",
@@ -1282,7 +1302,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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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);
@@ -78,8 +80,9 @@
 void request_unit_airlift(struct unit *punit, struct city *pcity);
 void request_unit_return(struct unit *punit);
 void request_unit_upgrade(struct unit *punit);
-void request_unit_wait(struct unit *punit);
+void request_units_wait(struct unit_list *punits);
 void request_unit_wakeup(struct unit *punit);
+void request_unit_select_same_type(struct unit_list *punits);
 void request_diplomat_action(enum diplomat_actions action, int dipl_id,
                             int target_id, int value);
 void request_toggle_city_outlines(void);
@@ -103,17 +106,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 +178,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, bool append);
+void key_unit_select_battlegroup(int battlegroup, bool append);
 
 /* 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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 11155)
+++ 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]