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

The first and most obvious part of the patch updates the tile/vision
documentation somewhat, as it was woefully out of date!

Renamed some of the enum known_type to reflect their source, and replaced
the order dependent < <= >= > tests.

Moved city_can_work_tile() out of server/citytools into common/city, to be
used in both client and server.  This meant changing it somewhat, as it
used some server-only functions.

The upside is a new feature: air and civilian units no longer prevent
working a tile.  This better conforms with usual expectations.  See new
unit_occupies_tile().

Although the purpose of this series of patches is removal of conflicts
between city_map[] and tile_worked(), there was (at least) one use that
wasn't duplicated:

  * "seen" tiles worked by cities that are not "seen" by the client!

The city_map[] has them marked TILE_UNAVAILABLE.  In the server, the tile
is set, but not in the client.

Now, the city id is passed to the client.  So the client has to make some
"invisible" cities (unknown center tile) to set the worked field.

That makes 2 client uses of NULL == city->tile (the other being the Editor).
The server still expects the tile field is always set.

Also, in borderless games, there's no tile_owner() to use for these
invisible cities, so an out-of-band (but in range) owner is used instead:
MAX_NUM_PLAYERS (in the reserved for barbarian range).  The owner is
corrected as soon as the city is seen, or a border expands.

This required some other small modifications, primarily LOG_DEBUG.

Extensively tested on savegames of multiple bug reports.  Seems to do what
is expected.  I'm sure many more edge cases will be found....

Index: doc/HACKING
===================================================================
--- doc/HACKING (revision 14420)
+++ doc/HACKING (working copy)
@@ -592,28 +592,29 @@
 Unknown tiles and Fog of War
 =========================================================================
 
-In the tile struct there is a field
+In common/tile.h, there are several fields:
 
 struct tile {
   ...
-  unsigned int known;
+  bv_player tile_known, tile_seen[V_COUNT];
   ...
 };
 
-On the server the known fields is considered to be a bitvector, one
-bit for each player, 0==tile unknown, 1==tile known.
-On the client this field contains one of the following 3 values:
+While tile_get_known() returns:
 
+/* network, order dependent */
 enum known_type {
- TILE_UNKNOWN, TILE_KNOWN_FOGGED, TILE_KNOWN
+ TILE_UNKNOWN = 0,
+ TILE_KNOWN_UNSEEN = 1,
+ TILE_KNOWN_SEEN = 2,
 };
 
-The values TILE_UNKNOWN, TILE_KNOWN are straightforward. TILE_FOGGED
-is a tile of which the user knows the terrain (inclusive cities, roads,
-etc...).
+The values TILE_UNKNOWN, TILE_KNOWN_SEEN are straightforward.
+TILE_KNOWN_UNSEEN is a tile of which the user knows the terrain,
+but not recent cities, roads, etc.
 
-TILE_UNKNOWN tiles are (or should be) never sent to the client.  In the past
-UNKNOWN tiles that were adjacent to FOGGED or KNOWN ones were sent to make
+TILE_UNKNOWN tiles never are (nor should be) sent to the client.  In the
+past, UNKNOWN tiles that were adjacent to UNSEEN or SEEN were sent to make
 the drawing process easier, but this has now been removed.  This means
 exploring new land may sometimes change the appearance of existing land (but
 this is not fundamentally different from what might happen when you
@@ -623,9 +624,10 @@
 Fog of war is the fact that even when you have seen a tile once you are
 not sent updates unless it is inside the sight range of one of your units
 or cities.
+
 We keep track of fog of war by counting the number of units and cities
 [and nifty future things like radar outposts] of each client that can
-see the tile. This requires a number per player, per tile, so each tile
+see the tile. This requires a number per player, per tile, so each player_tile
 has a short[]. Every time a unit/city/miscellaneous can observe a tile
 1 is added to its player's number at the tile, and when it can't observe
 any more (killed/moved/pillaged) 1 is subtracted. In addition to the
Index: server/cityhand.c
===================================================================
--- server/cityhand.c   (revision 14420)
+++ server/cityhand.c   (working copy)
@@ -185,11 +185,11 @@
     return;
   }
 
-  if (!city_can_work_tile(pcity, ptile)) {
+  if (0 == city_specialists(pcity)) {
     return;
   }
 
-  if (0 == city_specialists(pcity)) {
+  if (!city_can_work_tile(pcity, ptile)) {
     return;
   }
 
Index: server/citytools.c
===================================================================
--- server/citytools.c  (revision 14420)
+++ server/citytools.c  (working copy)
@@ -1979,40 +1979,6 @@
 }
 
 /**************************************************************************
-  Returns TRUE when a tile is available to be worked, or the city itself is
-  currently working the tile (and can continue).
-  city_x, city_y is in city map coords.
-**************************************************************************/
-bool city_can_work_tile(struct city *pcity, struct tile *ptile)
-{
-  struct player *powner = city_owner(pcity);
-
-  if (NULL == ptile) {
-    return FALSE;
-  }
-
-  if (NULL != tile_owner(ptile) && tile_owner(ptile) != powner) {
-    return FALSE;
-  }
-  /* TODO: civ3-like option for borders */
-
-  if (NULL != tile_worked(ptile) && tile_worked(ptile) != pcity) {
-    return FALSE;
-  }
-
-  if (!map_is_known_and_seen(ptile, powner, V_MAIN)) {
-    return FALSE;
-  }
-
-  if (is_enemy_unit_tile(ptile, powner)
-   && !is_free_worked(pcity, ptile)) {
-    return FALSE;
-  }
-
-  return TRUE;
-}
-
-/**************************************************************************
   Updates city map and tile map status, and affects adjacent cities.
   city_x, city_y is in city map coords.
   Call sync_cities() for the affected cities to be synced with the client.
Index: server/citytools.h
===================================================================
--- server/citytools.h  (revision 14420)
+++ server/citytools.h  (working copy)
@@ -83,8 +83,6 @@
 void city_freeze_workers(struct city *pcity);
 void city_thaw_workers(struct city *pcity);
 
-bool city_can_work_tile(struct city *pcity, struct tile *ptile);
-
 void city_map_update_empty(struct city *pcity, struct tile *ptile,
                           int city_x, int city_y);
 void city_map_update_worker(struct city *pcity, struct tile *ptile,
Index: server/maphand.c
===================================================================
--- server/maphand.c    (revision 14420)
+++ server/maphand.c    (working copy)
@@ -654,7 +654,7 @@
     }
 
     if (!pplayer || map_is_known_and_seen(ptile, pplayer, V_MAIN)) {
-      info.known = TILE_KNOWN;
+      info.known = TILE_KNOWN_SEEN;
       info.continent = tile_continent(ptile);
       info.owner = (NULL != tile_owner(ptile))
                     ? player_number(tile_owner(ptile))
@@ -679,7 +679,7 @@
               && map_get_seen(ptile, pplayer, V_MAIN) == 0) {
       struct player_tile *plrtile = map_get_player_tile(ptile, pplayer);
 
-      info.known = TILE_KNOWN_FOGGED;
+      info.known = TILE_KNOWN_UNSEEN;
       info.continent = tile_continent(ptile);
       info.owner = (NULL != tile_owner(ptile))
                    ? player_number(tile_owner(ptile))
Index: server/unittools.c
===================================================================
--- server/unittools.c  (revision 14420)
+++ server/unittools.c  (working copy)
@@ -2804,7 +2804,7 @@
       /* We're only concerned with known, unfogged tiles which may contain
        * hidden units that are no longer visible.  These units will not
        * have been handled by the fog code, above. */
-      if (tile_get_known(tile1, pplayer) == TILE_KNOWN) {
+      if (TILE_KNOWN_SEEN == tile_get_known(tile1, pplayer)) {
         unit_list_iterate(tile1->units, punit2) {
           if (punit2 != punit && !can_player_see_unit(pplayer, punit2)) {
            unit_goes_out_of_sight(pplayer, punit2);
Index: server/sanitycheck.c
===================================================================
--- server/sanitycheck.c        (revision 14420)
+++ server/sanitycheck.c        (working copy)
@@ -302,7 +302,7 @@
                  city_name(pcity), pcity->size,
                  is_city_center(pcity, ptile) ? "{city center}" : "");
        }
-       if (is_enemy_unit_tile(ptile, pplayer)) {
+       if (unit_occupies_tile(ptile, pplayer)) {
          SANITY_("(%4d,%4d) marked as empty, "
                  "but occupied by an enemy unit! "
                  "\"%s\"[%d]%s"),
@@ -338,7 +338,7 @@
                  city_name(pcity), pcity->size,
                  is_city_center(pcity, ptile) ? "{city center}" : "");
        }
-       if (is_enemy_unit_tile(ptile, pplayer)) {
+       if (unit_occupies_tile(ptile, pplayer)) {
          SANITY_("(%4d,%4d) marked as worked, "
                  "but occupied by an enemy unit! "
                  "\"%s\"[%d]%s"),
Index: common/unit.c
===================================================================
--- common/unit.c       (revision 14420)
+++ common/unit.c       (working copy)
@@ -1207,6 +1207,34 @@
   return NULL;
 }
 
+/****************************************************************************
+  Is there an occupying unit on this tile?
+
+  Intended for both client and server; assumes that hiding units are not
+  sent to client.  First check tile for known and seen.
+
+  called by city_can_work_tile().
+****************************************************************************/
+struct unit *unit_occupies_tile(const struct tile *ptile,
+                               const struct player *pplayer)
+{
+  unit_list_iterate(ptile->units, punit) {
+    if (!is_military_unit(punit)) {
+      continue;
+    }
+
+    if (is_air_unit(punit)) {
+      continue;
+    }
+
+    if (pplayers_at_war(unit_owner(punit), pplayer)) {
+      return punit;
+    }
+  } unit_list_iterate_end;
+
+  return NULL;
+}
+
 /**************************************************************************
   Is this square controlled by the pplayer?
 
@@ -1236,7 +1264,7 @@
 
       if (pcity 
           && (pcity->client.occupied 
-              || tile_get_known(ptile, pplayer) == TILE_KNOWN_FOGGED)) {
+              || TILE_KNOWN_UNSEEN == tile_get_known(ptile, pplayer))) {
         /* If the city is fogged, we assume it's occupied */
         return FALSE;
       }
Index: common/unit.h
===================================================================
--- common/unit.h       (revision 14420)
+++ common/unit.h       (working copy)
@@ -302,6 +302,8 @@
                                     const struct player *pplayer);
 struct unit *is_non_attack_unit_tile(const struct tile *ptile,
                                     const struct player *pplayer);
+struct unit *unit_occupies_tile(const struct tile *ptile,
+                               const struct player *pplayer);
 
 bool is_my_zoc(const struct player *unit_owner, const struct tile *ptile);
 bool unit_being_aggressive(const struct unit *punit);
Index: common/city.c
===================================================================
--- common/city.c       (revision 14420)
+++ common/city.c       (working copy)
@@ -862,6 +862,39 @@
 }
 
 /**************************************************************************
+  Returns TRUE when a tile is available to be worked, or the city itself is
+  currently working the tile (and can continue).
+**************************************************************************/
+bool city_can_work_tile(const struct city *pcity, const struct tile *ptile)
+{
+  struct player *powner = city_owner(pcity);
+
+  if (NULL == ptile) {
+    return FALSE;
+  }
+
+  if (NULL != tile_owner(ptile) && tile_owner(ptile) != powner) {
+    return FALSE;
+  }
+  /* TODO: civ3-like option for borders */
+
+  if (NULL != tile_worked(ptile) && tile_worked(ptile) != pcity) {
+    return FALSE;
+  }
+
+  if (TILE_KNOWN_SEEN != tile_get_known(ptile, powner)) {
+    return FALSE;
+  }
+
+  if (!is_free_worked(pcity, ptile)
+   && NULL != unit_occupies_tile(ptile, powner)) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**************************************************************************
   Returns TRUE if the given unit can build a city at the given map
   coordinates.
 
Index: common/city.h
===================================================================
--- common/city.h       (revision 14420)
+++ common/city.h       (working copy)
@@ -258,7 +258,7 @@
 
 struct city {
   char name[MAX_LEN_NAME];
-  struct tile *tile; /* May be NULL in Editor! */
+  struct tile *tile; /* May be NULL, should check! */
   struct player *owner; /* Cannot be NULL. */
   struct player *original; /* Cannot be NULL. */
   int id;
@@ -507,6 +507,8 @@
 int city_tile_output_now(const struct city *pcity, const struct tile *ptile,
                         Output_type_id otype);
 
+bool city_can_work_tile(const struct city *pcity, const struct tile *ptile);
+
 bool city_can_be_built_here(const struct tile *ptile,
                            const struct unit *punit);
 
Index: common/aicore/pf_tools.c
===================================================================
--- common/aicore/pf_tools.c    (revision 14420)
+++ common/aicore/pf_tools.c    (working copy)
@@ -582,7 +582,7 @@
     return FALSE;
   }
 
-  if (TILE_KNOWN == known
+  if (TILE_KNOWN_SEEN == known
    && is_enemy_unit_tile(ptile, param->owner)) {
     /* don't reveal unknown units */
     return FALSE;
Index: common/aicore/path_finding.c
===================================================================
--- common/aicore/path_finding.c        (revision 14420)
+++ common/aicore/path_finding.c        (working copy)
@@ -221,7 +221,7 @@
 
   /* Establish the "known" status of node */
   if (params->omniscience) {
-    node->node_known_type = TILE_KNOWN;
+    node->node_known_type = TILE_KNOWN_SEEN;
   } else {
     node->node_known_type = tile_get_known(ptile, params->owner);
   }
Index: common/tile.c
===================================================================
--- common/tile.c       (revision 14420)
+++ common/tile.c       (working copy)
@@ -323,9 +323,9 @@
   if (!BV_ISSET(ptile->tile_known, player_index(pplayer))) {
     return TILE_UNKNOWN;
   } else if (!BV_ISSET(ptile->tile_seen[V_MAIN], player_index(pplayer))) {
-    return TILE_KNOWN_FOGGED;
+    return TILE_KNOWN_UNSEEN;
   } else {
-    return TILE_KNOWN;
+    return TILE_KNOWN_SEEN;
   }
 }
 
Index: common/tile.h
===================================================================
--- common/tile.h       (revision 14420)
+++ common/tile.h       (working copy)
@@ -23,8 +23,8 @@
 /* network, order dependent */
 enum known_type {
  TILE_UNKNOWN = 0,
- TILE_KNOWN_FOGGED = 1,
- TILE_KNOWN = 2,
+ TILE_KNOWN_UNSEEN = 1,
+ TILE_KNOWN_SEEN = 2,
 };
 
 /* Convenience macro for accessing tile coordinates.  This should only be
Index: common/player.c
===================================================================
--- common/player.c     (revision 14420)
+++ common/player.c     (working copy)
@@ -435,7 +435,7 @@
   struct city *pcity;
 
   /* If the player can't even see the tile... */
-  if (tile_get_known(ptile, pplayer) != TILE_KNOWN) {
+  if (TILE_KNOWN_SEEN != tile_get_known(ptile, pplayer)) {
     return FALSE;
   }
 
Index: common/game.c
===================================================================
--- common/game.c       (revision 14420)
+++ common/game.c       (working copy)
@@ -206,25 +206,34 @@
   struct tile *pcenter = city_tile(pcity);
   struct player *powner = city_owner(pcity);
 
-  freelog(LOG_DEBUG, "game_remove_city()"
-          " at (%d,%d) city %d, %s %s",
-         TILE_XY(pcenter),
-         pcity->id,
-         nation_rule_name(nation_of_player(powner)),
-         city_name(pcity));
+  if (NULL != powner) {
+    /* always unlink before clearing data */
+    city_list_unlink(powner->cities, pcity);
+  }
 
+  if (NULL == pcenter) {
+    freelog(LOG_DEBUG, "game_remove_city()"
+            " virtual city %d, %s",
+            pcity->id,
+            city_name(pcity));
+  } else {
+    freelog(LOG_DEBUG, "game_remove_city()"
+            " at (%d,%d) city %d, %s %s",
+            TILE_XY(pcenter),
+            pcity->id,
+            nation_rule_name(nation_of_player(powner)),
+            city_name(pcity));
+
+    city_tile_iterate(pcenter, ptile) {
+      if (tile_worked(ptile) == pcity) {
+        tile_set_worked(ptile, NULL);
+      }
+    } city_tile_iterate_end;
+  }
+  
   /* Opaque server-only variable: the server must free this earlier. */
   assert(pcity->server.vision == NULL);
 
-  /* always unlink before clearing data */
-  city_list_unlink(powner->cities, pcity);
-
-  city_tile_iterate(pcenter, ptile) {
-    if (tile_worked(ptile) == pcity) {
-      tile_set_worked(ptile, NULL);
-    }
-  } city_tile_iterate_end;
-
   idex_unregister_city(pcity);
   destroy_city_virtual(pcity);
 }
Index: client/citydlg_common.c
===================================================================
--- client/citydlg_common.c     (revision 14420)
+++ client/citydlg_common.c     (working copy)
@@ -894,12 +894,17 @@
 **************************************************************************/
 int city_toggle_worker(struct city *pcity, int city_x, int city_y)
 {
+  struct tile *ptile = city_map_to_tile(city_tile(pcity), city_x, city_y);
+
   assert(is_valid_city_coords(city_x, city_y));
+  if (NULL == ptile) {
+    return 0;
+  }
 
-  if (pcity->city_map[city_x][city_y] == C_TILE_WORKER) {
+  if (NULL != tile_worked(ptile) && tile_worked(ptile) == pcity) {
     return dsend_packet_city_make_specialist(&aconnection, pcity->id, city_x,
                                             city_y);
-  } else if (pcity->city_map[city_x][city_y] == C_TILE_EMPTY) {
+  } else if (city_can_work_tile(pcity, ptile)) {
     return dsend_packet_city_make_worker(&aconnection, pcity->id, city_x,
                                         city_y);
   } else {
Index: client/overview_common.c
===================================================================
--- client/overview_common.c    (revision 14420)
+++ client/overview_common.c    (working copy)
@@ -356,8 +356,8 @@
   canvas_put_rectangle(pcanvas,
                       overview_tile_color(ptile),
                       x, y, w, h);
-  if (client_tile_get_known(ptile) == TILE_KNOWN_FOGGED
-      && overview.fog) {
+  if (overview.fog
+      && TILE_KNOWN_UNSEEN == client_tile_get_known(ptile)) {
     canvas_put_sprite(pcanvas, x, y, get_basic_fog_sprite(tileset),
                      0, 0, w, h);
   }
Index: client/gui-gtk-2.0/mapctrl.c
===================================================================
--- client/gui-gtk-2.0/mapctrl.c        (revision 14420)
+++ client/gui-gtk-2.0/mapctrl.c        (working copy)
@@ -117,7 +117,7 @@
   struct unit *punit;
   bool is_orders;
 
-  if (client_tile_get_known(ptile) >= TILE_KNOWN_FOGGED) {
+  if (TILE_UNKNOWN != client_tile_get_known(ptile)) {
     p=gtk_window_new(GTK_WINDOW_POPUP);
     gtk_widget_set_app_paintable(p, TRUE);
     gtk_container_set_border_width(GTK_CONTAINER(p), 4);
Index: client/gui-xaw/mapctrl.c
===================================================================
--- client/gui-xaw/mapctrl.c    (revision 14420)
+++ client/gui-xaw/mapctrl.c    (working copy)
@@ -106,7 +106,7 @@
   char *content;
   static bool is_orders;
   
-  if (client_tile_get_known(ptile)>=TILE_KNOWN_FOGGED) {
+  if (TILE_UNKNOWN != client_tile_get_known(ptile)) {
     Widget p=XtCreatePopupShell("popupinfo", simpleMenuWidgetClass,
                                map_canvas, NULL, 0);
     content = (char *) popup_info_text(ptile);
Index: client/gui-win32/mapctrl.c
===================================================================
--- client/gui-win32/mapctrl.c  (revision 14420)
+++ client/gui-win32/mapctrl.c  (working copy)
@@ -121,7 +121,7 @@
     popit_popup=NULL;
   }
   
-  if (client_tile_get_known(ptile) < TILE_KNOWN_FOGGED)
+  if (TILE_UNKNOWN == client_tile_get_known(ptile))
     return;
   
   popup=fcwin_create_layouted_window(popit_proc,NULL,WS_POPUP|WS_BORDER,
Index: client/packhand.c
===================================================================
--- client/packhand.c   (revision 14420)
+++ client/packhand.c   (working copy)
@@ -73,12 +73,46 @@
 
 #include "packhand.h"
 
-static void handle_city_packet_common(struct city *pcity, bool is_new,
-                                      bool popup, bool investigate);
+static void city_packet_common(struct city *pcity, struct tile *pcenter,
+                               struct player *powner, bool is_new,
+                               bool popup, bool investigate);
 static bool handle_unit_packet_common(struct unit *packet_unit);
+
+
 static int *reports_thaw_requests = NULL;
 static int reports_thaw_requests_size = 0;
 
+/* The dumbest of cities, placeholders for unknown and unseen cities. */
+static struct city_list *invisible_cities = NULL;
+
+
+/****************************************************************************
+  Called below, and by client/civclient.c client_game_free()
+****************************************************************************/
+void packhand_free(void)
+{
+  if (NULL != invisible_cities) {
+    city_list_iterate(invisible_cities, pcity) {
+      idex_unregister_city(pcity);
+      destroy_city_virtual(pcity);
+    } city_list_iterate_end;
+
+    city_list_unlink_all(invisible_cities);
+    city_list_free(invisible_cities);
+    invisible_cities = NULL;
+  }
+}
+
+/****************************************************************************
+  Called only by handle_map_info() below.
+****************************************************************************/
+static void packhand_init(void)
+{
+  packhand_free();
+
+  invisible_cities = city_list_new();
+}
+
 /**************************************************************************
   Unpackage the unit information into a newly allocated unit structure.
 **************************************************************************/
@@ -228,14 +262,16 @@
 void handle_city_remove(int city_id)
 {
   struct city *pcity = game_find_city_by_number(city_id);
-  struct tile *ptile;
 
-  if (!pcity)
+  if (NULL == pcity) {
+    freelog(LOG_ERROR,
+           "handle_city_remove() bad city %d.",
+           city_id);
     return;
+  }
 
   agents_city_remove(pcity);
 
-  ptile = pcity->tile;
   client_remove_city(pcity);
 
   /* update menus if the focus unit is on the tile. */
@@ -422,9 +458,10 @@
   struct unit_list *pfocus_units = get_units_in_focus();
   struct city *pcity = game_find_city_by_number(packet->id);
   struct tile *pcenter = map_pos_to_tile(packet->x, packet->y);
-  struct player *pplayer = valid_player_by_number(packet->owner);
+  struct tile *ptile = NULL;
+  struct player *powner = valid_player_by_number(packet->owner);
 
-  if (NULL == pplayer) {
+  if (NULL == powner) {
     freelog(LOG_ERROR,
             "handle_city_info() bad player number %d.",
             packet->owner);
@@ -438,12 +475,6 @@
     return;
   }
 
-  if (pcity && (player_number(city_owner(pcity)) != packet->owner)) {
-    client_remove_city(pcity);
-    pcity = NULL;
-    city_has_changed_owner = TRUE;
-  }
-
   if (packet->production_kind < VUT_NONE || packet->production_kind >= 
VUT_LAST) {
     freelog(LOG_ERROR, "handle_city_info()"
             " bad production_kind %d.",
@@ -461,10 +492,32 @@
     }
   }
 
+  if (NULL != pcity) {
+    ptile = city_tile(pcity);
+
+    if (NULL == ptile) {
+      /* invisible worked city */
+      city_list_unlink(invisible_cities, pcity);
+      city_is_new = TRUE;
+
+      pcity->tile =
+      ptile = pcenter;
+      pcity->owner =
+      pcity->original = powner;
+
+      tile_set_owner(pcenter, powner);
+    } else if (tile_owner(ptile) != powner) {
+      /* assumes the tile properly reflects city_owner() */
+      client_remove_city(pcity);
+      pcity = NULL;
+      city_has_changed_owner = TRUE;
+    }
+  }
+
   if (NULL == pcity) {
     city_is_new = TRUE;
-    pcity = create_city_virtual(pplayer, pcenter, packet->name);
-    tile_set_owner(pcenter, pplayer);
+    pcity = create_city_virtual(powner, pcenter, packet->name);
+    tile_set_owner(pcenter, powner);
     pcity->id = packet->id;
     idex_register_city(pcity);
     update_descriptions = TRUE;
@@ -474,14 +527,14 @@
             pcity->id,
             packet->id);
     return;
-  } else if (city_tile(pcity) != pcenter) {
+  } else if (ptile != pcenter) {
     freelog(LOG_ERROR, "handle_city_info()"
             " city tile (%d,%d) != (%d,%d).",
-            TILE_XY(city_tile(pcity)),
+            TILE_XY(ptile),
             TILE_XY(packet));
     return;
   } else {
-    name_changed = (strcmp(city_name(pcity), packet->name) != 0);
+    name_changed = (0 != strncmp(packet->name, pcity->name, 
strlen(pcity->name)));
 
     /* Check if city desciptions should be updated */
     if (draw_city_names && name_changed) {
@@ -559,6 +612,7 @@
   }
   pcity->production = product;
 
+#ifdef DONE_BY_create_city_virtual
   if (city_is_new) {
     init_worklist(&pcity->worklist);
 
@@ -566,6 +620,8 @@
       pcity->built[i].turn = I_NEVER;
     }
   }
+#endif
+
   copy_worklist(&pcity->worklist, &packet->worklist);
   pcity->did_buy=packet->did_buy;
   pcity->did_sell=packet->did_sell;
@@ -605,7 +661,7 @@
     }
 
     if (is_valid_city_coords(x, y)) {
-      struct tile *ptile = city_map_to_tile(pcenter, x, y);
+      ptile = city_map_to_tile(pcenter, x, y);
 
       city_map_update(pcity, ptile, x, y, packet->city_map[i]);
     }
@@ -633,7 +689,8 @@
   pcity->client.unhappy = city_unhappy(pcity);
 
   popup = (city_is_new && can_client_change_view()
-           && city_owner(pcity) == client.playing && popup_new_cities)
+           && powner == client.playing
+           && popup_new_cities)
           || packet->diplomat_investigate;
 
   if (city_is_new && !city_has_changed_owner) {
@@ -644,8 +701,8 @@
 
   pcity->client.walls = packet->walls;
 
-  handle_city_packet_common(pcity, city_is_new, popup,
-                           packet->diplomat_investigate);
+  city_packet_common(pcity, pcenter, powner, city_is_new, popup,
+                     packet->diplomat_investigate);
 
   /* Update the description if necessary. */
   if (update_descriptions) {
@@ -683,8 +740,9 @@
   Naturally, both require many of the same operations to be done on the
   data.
 ****************************************************************************/
-static void handle_city_packet_common(struct city *pcity, bool is_new,
-                                      bool popup, bool investigate)
+static void city_packet_common(struct city *pcity, struct tile *pcenter,
+                               struct player *powner, bool is_new,
+                               bool popup, bool investigate)
 {
   if (is_new) {
     pcity->units_supported = unit_list_new();
@@ -693,10 +751,10 @@
 
     /* redundant to city_map_update() in handle_city_info(),
      * but needed for handle_city_short_info() */
-    tile_set_worked(pcity->tile, pcity); /* is_free_worked() */
-    city_list_prepend(city_owner(pcity)->cities, pcity);
+    tile_set_worked(pcenter, pcity); /* is_free_worked() */
+    city_list_prepend(powner->cities, pcity);
 
-    if (city_owner(pcity) == client.playing) {
+    if (powner == client.playing) {
       city_report_dialog_update();
     }
 
@@ -707,13 +765,13 @@
       unit_list_iterate_end;
     } players_iterate_end;
   } else {
-    if (city_owner(pcity) == client.playing) {
+    if (powner == client.playing) {
       city_report_dialog_update_city(pcity);
     }
   }
 
   if (can_client_change_view()) {
-    refresh_city_mapcanvas(pcity, pcity->tile, FALSE, FALSE);
+    refresh_city_mapcanvas(pcity, pcenter, FALSE, FALSE);
   }
 
   if (city_workers_display==pcity)  {
@@ -736,14 +794,16 @@
   }
 
   /* update menus if the focus unit is on the tile. */
-  if (get_focus_unit_on_tile(pcity->tile)) {
+  if (get_focus_unit_on_tile(pcenter)) {
     update_menus();
   }
 
-  if(is_new) {
-    freelog(LOG_DEBUG, "New %s city %s id %d (%d %d)",
+  if (is_new) {
+    freelog(LOG_DEBUG, "(%d,%d) creating city %d, %s %s",
+           TILE_XY(pcenter),
+           pcity->id,
            nation_rule_name(nation_of_city(pcity)),
-           city_name(pcity), pcity->id, TILE_XY(pcity->tile));
+           city_name(pcity));
   }
 }
 
@@ -756,12 +816,14 @@
 {
   bool city_has_changed_owner = FALSE;
   bool city_is_new = FALSE;
+  bool name_changed = FALSE;
   bool update_descriptions = FALSE;
   struct city *pcity = game_find_city_by_number(packet->id);
   struct tile *pcenter = map_pos_to_tile(packet->x, packet->y);
-  struct player *pplayer = valid_player_by_number(packet->owner);
+  struct tile *ptile = NULL;
+  struct player *powner = valid_player_by_number(packet->owner);
 
-  if (NULL == pplayer) {
+  if (NULL == powner) {
     freelog(LOG_ERROR,
             "handle_city_short_info() bad player number %d.",
             packet->owner);
@@ -775,16 +837,32 @@
     return;
   }
 
-  if (pcity && city_owner(pcity) != pplayer) {
-    client_remove_city(pcity);
-    pcity = NULL;
-    city_has_changed_owner = TRUE;
+  if (NULL != pcity) {
+    ptile = city_tile(pcity);
+
+    if (NULL == ptile) {
+      /* invisible worked city */
+      city_list_unlink(invisible_cities, pcity);
+      city_is_new = TRUE;
+
+      pcity->tile =
+      ptile = pcenter;
+      pcity->owner =
+      pcity->original = powner;
+
+      tile_set_owner(pcenter, powner);
+    } else if (tile_owner(ptile) != powner) {
+      /* assumes the tile properly reflects city_owner() */
+      client_remove_city(pcity);
+      pcity = NULL;
+      city_has_changed_owner = TRUE;
+    }
   }
 
   if (NULL == pcity) {
     city_is_new = TRUE;
-    pcity = create_city_virtual(pplayer, pcenter, packet->name);
-    tile_set_owner(pcenter, pplayer);
+    pcity = create_city_virtual(powner, pcenter, packet->name);
+    tile_set_owner(pcenter, powner);
     pcity->id = packet->id;
     idex_register_city(pcity);
   } else if (pcity->id != packet->id) {
@@ -800,18 +878,19 @@
             TILE_XY(packet));
     return;
   } else {
+    name_changed = (0 != strncmp(packet->name, pcity->name, 
strlen(pcity->name)));
+
     /* Check if city desciptions should be updated */
-    if (draw_city_names
-     && 0 != strncmp(packet->name, pcity->name, strlen(pcity->name))) {
+    if (draw_city_names && name_changed) {
       update_descriptions = TRUE;
     }
 
-    tile_set_owner(pcenter, pplayer);
     sz_strlcpy(pcity->name, packet->name);
     
     memset(pcity->feel, 0, sizeof(pcity->feel));
     memset(pcity->specialists, 0, sizeof(pcity->specialists));
   }
+
   pcity->specialists[DEFAULT_SPECIALIST] =
   pcity->size = packet->size;
 
@@ -824,6 +903,7 @@
   pcity->client.happy = packet->happy;
   pcity->client.unhappy = packet->unhappy;
 
+#ifdef DONE_BY_create_city_virtual
   if (city_is_new) {
     int i;
 
@@ -831,6 +911,7 @@
       pcity->built[i].turn = I_NEVER;
     }
   }
+#endif
 
   improvement_iterate(pimprove) {
     bool have = BV_ISSET(packet->improvements, improvement_index(pimprove));
@@ -877,7 +958,7 @@
 
   pcity->client.walls = packet->walls;
 
-  handle_city_packet_common(pcity, city_is_new, FALSE, FALSE);
+  city_packet_common(pcity, pcenter, powner, city_is_new, FALSE, FALSE);
 
   /* Update the description if necessary. */
   if (update_descriptions) {
@@ -1493,6 +1574,8 @@
   generate_citydlg_dimensions();
 
   calculate_overview_dimensions();
+
+  packhand_init();
 }
 
 /**************************************************************************
@@ -2107,6 +2190,7 @@
 **************************************************************************/
 void handle_tile_info(struct packet_tile_info *packet)
 {
+  enum known_type new_known;
   enum known_type old_known;
   bool known_changed = FALSE;
   bool tile_changed = FALSE;
@@ -2129,8 +2213,8 @@
     case TILE_UNKNOWN:
       tile_set_terrain(ptile, pterrain);
       break;
-    case TILE_KNOWN_FOGGED:
-    case TILE_KNOWN:
+    case TILE_KNOWN_UNSEEN:
+    case TILE_KNOWN_SEEN:
       if (NULL != pterrain || TILE_UNKNOWN == packet->known) {
         tile_set_terrain(ptile, pterrain);
       } else {
@@ -2167,6 +2251,51 @@
     tile_changed = TRUE;
   }
 
+  if (NULL == tile_worked(ptile)
+   || tile_worked(ptile)->id != packet->worked) {
+    if (IDENTITY_NUMBER_ZERO != packet->worked) {
+      struct city *pwork = game_find_city_by_number(packet->worked);
+
+      if (NULL == pwork) {
+        char named[MAX_LEN_NAME];
+        struct player *placeholder = powner;
+
+        /* new unseen city, or before city_info */
+        if (NULL == placeholder) {
+          /* worker outside border allowed in earlier versions,
+           * use non-player as placeholder.
+           */
+          placeholder = player_by_number(MAX_NUM_PLAYERS);
+        }
+        my_snprintf(named, sizeof(named), "%06u", packet->worked);
+
+        pwork = create_city_virtual(placeholder, NULL, named);
+        pwork->id = packet->worked;
+        idex_register_city(pwork);
+
+        city_list_prepend(invisible_cities, pwork);
+
+        freelog(LOG_DEBUG, "(%d,%d) invisible city %d, %s",
+                TILE_XY(ptile),
+                pwork->id,
+                city_name(pwork));
+      } else if (NULL == city_tile(pwork)) {
+        /* old unseen city, or before city_info */
+        if (NULL != powner && city_owner(pwork) != powner) {
+          /* update placeholder with current owner */
+          pwork->owner =
+          pwork->original = powner;
+        }
+      }
+
+      tile_set_worked(ptile, pwork);
+    } else {
+      tile_set_worked(ptile, NULL);
+    }
+
+    tile_changed = TRUE;
+  }
+
   if (old_known != packet->known) {
     known_changed = TRUE;
   }
@@ -2178,13 +2307,13 @@
     } vision_layer_iterate_end;
 
     switch (packet->known) {
-    case TILE_KNOWN:
+    case TILE_KNOWN_SEEN:
       BV_SET(ptile->tile_known, player_index(client.playing));
       vision_layer_iterate(v) {
        BV_SET(ptile->tile_seen[v], player_index(client.playing));
       } vision_layer_iterate_end;
       break;
-    case TILE_KNOWN_FOGGED:
+    case TILE_KNOWN_UNSEEN:
       BV_SET(ptile->tile_known, player_index(client.playing));
       break;
     case TILE_UNKNOWN:
@@ -2196,6 +2325,7 @@
       break;
     };
   }
+  new_known = client_tile_get_known(ptile);
 
   if (packet->spec_sprite[0] != '\0') {
     if (!ptile->spec_sprite
@@ -2214,8 +2344,7 @@
     }
   }
 
-  if (client_tile_get_known(ptile) <= TILE_KNOWN_FOGGED
-      && old_known == TILE_KNOWN) {
+  if (TILE_KNOWN_SEEN == old_known && TILE_KNOWN_SEEN != new_known) {
     /* This is an error.  So first we log the error, then make an assertion.
      * But for NDEBUG clients we fix the error. */
     unit_list_iterate(ptile->units, punit) {
@@ -2250,9 +2379,9 @@
      * A tile can only change if it was known before and is still
      * known. In the other cases the tile is new or removed.
      */
-    if (known_changed && client_tile_get_known(ptile) == TILE_KNOWN) {
+    if (known_changed && TILE_KNOWN_SEEN == new_known) {
       agents_tile_new(ptile);
-    } else if (known_changed && client_tile_get_known(ptile) == 
TILE_KNOWN_FOGGED) {
+    } else if (known_changed && TILE_KNOWN_UNSEEN == new_known) {
       agents_tile_remove(ptile);
     } else {
       agents_tile_changed(ptile);
@@ -2262,7 +2391,7 @@
   /* refresh tiles */
   if (can_client_change_view()) {
     /* the tile itself (including the necessary parts of adjacent tiles) */
-    if (tile_changed || old_known != client_tile_get_known(ptile)) {
+    if (tile_changed || old_known != new_known) {
       refresh_tile_mapcanvas(ptile, TRUE, FALSE);
     }
   }
Index: client/packhand.h
===================================================================
--- client/packhand.h   (revision 14420)
+++ client/packhand.h   (working copy)
@@ -20,6 +20,8 @@
 
 #include "packhand_gen.h"
 
+void packhand_free(void);
+
 void notify_about_incoming_packet(struct connection *pc,
                                   int packet_type, int size);
 void notify_about_outgoing_packet(struct connection *pc,
Index: client/climap.c
===================================================================
--- client/climap.c     (revision 14420)
+++ client/climap.c     (working copy)
@@ -36,7 +36,7 @@
 {
   if (!client.playing) {
     if (client_is_observer()) {
-      return TILE_KNOWN;
+      return TILE_KNOWN_SEEN;
     } else {
       return TILE_UNKNOWN;
     }
Index: client/tilespec.c
===================================================================
--- client/tilespec.c   (revision 14420)
+++ client/tilespec.c   (working copy)
@@ -3601,14 +3601,16 @@
                                           const struct city *citymode)
 {
   const struct city *pcity;
+  const struct city *pwork;
   struct unit *psettler;
   struct drawn_sprite *saved_sprs = sprs;
   int city_x, city_y;
   const int NUM_CITY_COLORS = t->sprites.city.worked_tile_overlay.size;
 
-  if (!ptile || client_tile_get_known(ptile) == TILE_UNKNOWN) {
+  if (NULL == ptile || TILE_UNKNOWN == client_tile_get_known(ptile)) {
     return 0;
   }
+  pwork = tile_worked(ptile);
 
   if (citymode) {
     pcity = citymode;
@@ -3617,23 +3619,18 @@
   }
 
   if (pcity && city_base_to_city_map(&city_x, &city_y, pcity, ptile)) {
-    enum city_tile_type ctt = city_map_status(pcity, city_x, city_y);
+//    enum city_tile_type ctt = city_map_status(pcity, city_x, city_y);
 
     if (!citymode && pcity->client.colored) {
       /* Add citymap overlay for a city. */
       int index = pcity->client.color_index % NUM_CITY_COLORS;
 
-      switch (ctt) {
-      case C_TILE_EMPTY:
-       ADD_SPRITE_SIMPLE(t->sprites.city.unworked_tile_overlay.p[index]);
-       break;
-      case C_TILE_WORKER:
-       ADD_SPRITE_SIMPLE(t->sprites.city.worked_tile_overlay.p[index]);
-       break;
-      case C_TILE_UNAVAILABLE:
-       break;
+      if (NULL != pwork && pwork == pcity) {
+        ADD_SPRITE_SIMPLE(t->sprites.city.worked_tile_overlay.p[index]);
+      } else if (city_can_work_tile(pcity, ptile)) {
+        ADD_SPRITE_SIMPLE(t->sprites.city.unworked_tile_overlay.p[index]);
       }
-    } else if (C_TILE_WORKER == ctt) {
+    } else if (NULL != pwork && pwork == pcity) {
       /* Add on the tile output sprites. */
       int food = city_tile_output_now(pcity, ptile, O_FOOD);
       int shields = city_tile_output_now(pcity, ptile, O_SHIELD);
@@ -3712,7 +3709,8 @@
   struct drawn_sprite *saved_sprs = sprs;
 
   if (t->fogstyle == FOG_SPRITE && draw_fog_of_war
-      && ptile && client_tile_get_known(ptile) == TILE_KNOWN_FOGGED) {
+      && NULL != ptile
+      && TILE_KNOWN_UNSEEN == client_tile_get_known(ptile)) {
     /* With FOG_AUTO, fog is done this way. */
     ADD_SPRITE_SIMPLE(t->sprites.tx.fog);
   }
@@ -3728,10 +3726,10 @@
        value = fogged;
       } else {
        switch (client_tile_get_known(pcorner->tile[i])) {
-       case TILE_KNOWN:
+       case TILE_KNOWN_SEEN:
          value = known;
          break;
-       case TILE_KNOWN_FOGGED:
+       case TILE_KNOWN_UNSEEN:
          value = fogged;
          break;
        case TILE_UNKNOWN:
@@ -4110,12 +4108,13 @@
        }
       }
     }
-  } else if (ptile && client_tile_get_known(ptile) != TILE_UNKNOWN) {
+  } else if (NULL != ptile && TILE_UNKNOWN != client_tile_get_known(ptile)) {
     int cx, cy;
 
     if (citymode
-        && city_base_to_city_map(&cx, &cy, citymode, ptile)
-        && citymode->city_map[cx][cy] == C_TILE_UNAVAILABLE) {
+        /* test to ensure valid coordinates? */
+     && city_base_to_city_map(&cx, &cy, citymode, ptile)
+     && !city_can_work_tile(citymode, ptile)) {
       ADD_SPRITE_SIMPLE(t->sprites.grid.unavailable);
     }
   }
Index: client/mapctrl_common.c
===================================================================
--- client/mapctrl_common.c     (revision 14420)
+++ client/mapctrl_common.c     (working copy)
@@ -561,29 +561,25 @@
 **************************************************************************/
 void adjust_workers_button_pressed(int canvas_x, int canvas_y)
 {
-  int city_x, city_y;
   struct tile *ptile = canvas_pos_to_tile(canvas_x, canvas_y);
 
-  if (can_client_issue_orders() && ptile) {
+  if (NULL != ptile && can_client_issue_orders()) {
     struct city *pcity = find_city_near_tile(ptile);
 
     if (pcity && !cma_is_city_under_agent(pcity, NULL)) {
-      if (!city_base_to_city_map(&city_x, &city_y, pcity, ptile)) {
-       assert(0);
-      }
+      int city_x, city_y;
 
-      switch (city_map_status(pcity, city_x, city_y)) {
-      case C_TILE_WORKER:
+      assert(city_base_to_city_map(&city_x, &city_y, pcity, ptile));
+
+      if (NULL != tile_worked(ptile) && tile_worked(ptile) == pcity) {
        dsend_packet_city_make_specialist(&aconnection, pcity->id,
                                          city_x, city_y);
-       break;
-      case C_TILE_EMPTY:
+      } else if (city_can_work_tile(pcity, ptile)) {
        dsend_packet_city_make_worker(&aconnection, pcity->id,
                                      city_x, city_y);
-       break;
-      case C_TILE_UNAVAILABLE:
+      } else {
        return;
-      };
+      }
 
       /* When the city info packet is received, update the workers on the
        * map.  This is a bad hack used to selectively update the mapview
Index: client/civclient.c
===================================================================
--- client/civclient.c  (revision 14420)
+++ client/civclient.c  (working copy)
@@ -189,6 +189,7 @@
 **************************************************************************/
 static void client_game_free(void)
 {
+  packhand_free();
   control_done();
   free_help_texts();
   attribute_free();
Index: client/mapview_common.c
===================================================================
--- client/mapview_common.c     (revision 14420)
+++ client/mapview_common.c     (working copy)
@@ -895,7 +895,7 @@
                                ptile, pedge, pcorner,
                                punit, pcity, citymode);
   bool fog = (ptile && draw_fog_of_war
-             && client_tile_get_known(ptile) == TILE_KNOWN_FOGGED);
+             && TILE_KNOWN_UNSEEN == client_tile_get_known(ptile));
 
   /*** Draw terrain and specials ***/
   put_drawn_sprites(pcanvas, canvas_x, canvas_y, count, tile_sprs, fog);
@@ -1840,18 +1840,15 @@
   /* rule e */
   closest_city = NULL;
 
-  city_tile_iterate_cxy(ptile, tile1, city_x, city_y) {
+  city_tile_iterate(ptile, tile1) {
     pcity = tile_city(tile1);
     if (pcity
        && (!client.playing || city_owner(pcity) == client.playing)
-       && C_TILE_EMPTY == city_map_status(pcity,
-                                          CITY_MAP_SIZE - 1 - city_x,
-                                          CITY_MAP_SIZE - 1 - city_y)) {
+       && city_can_work_tile(pcity, tile1)) {
       /*
        * Note, we must explicitly check if the tile is workable (with
-       * city_map_status() above), since it is possible that another
-       * city (perhaps an unseen enemy city) may be working it,
-       * causing it to be marked as C_TILE_UNAVAILABLE.
+       * city_can_work_tile() above), since it is possible that another
+       * city (perhaps an UNSEEN city) may be working it!
        */
       
       if (map_deco[tile_index(pcity->tile)].hilite == HILITE_CITY) {
@@ -1862,7 +1859,7 @@
        closest_city = pcity;
       }
     }
-  } city_tile_iterate_cxy_end;
+  } city_tile_iterate_end;
 
   /* rule d */
   if (closest_city || !punit) {
Index: client/climisc.c
===================================================================
--- client/climisc.c    (revision 14420)
+++ client/climisc.c    (working copy)
@@ -135,13 +135,12 @@
 void client_remove_city(struct city *pcity)
 {
   bool effect_update;
-  struct tile *ptile = pcity->tile;
+  struct tile *ptile = city_tile(pcity);
   struct city old_city = *pcity;
 
-  freelog(LOG_DEBUG, "removing city %s, %s, (%d %d)",
-         city_name(pcity),
-         nation_rule_name(nation_of_city(pcity)),
-         TILE_XY(ptile));
+  freelog(LOG_DEBUG, "client_remove_city() %d, %s",
+         pcity->id,
+         city_name(pcity));
 
   /* Explicitly remove all improvements, to properly remove any global effects
      and to handle the preservation of "destroyed" effects. */
Index: client/editor.c
===================================================================
--- client/editor.c     (revision 14420)
+++ client/editor.c     (working copy)
@@ -300,8 +300,8 @@
 {
   /* Editing tiles that we can't see (or are fogged) will only lead to
    * problems. */
-  if (selected_tool != ETOOL_VISION &&
-      client_tile_get_known(ptile) != TILE_KNOWN) {
+  if (ETOOL_VISION != selected_tool
+   && TILE_KNOWN_SEEN != client_tile_get_known(ptile)) {
     return CURSOR_INVALID;
   }
 
_______________________________________________
Freeciv-dev mailing list
Freeciv-dev@gna.org
https://mail.gna.org/listinfo/freeciv-dev

Reply via email to