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

I have made some minor changes to the combined v6 patches (the
base migration patch v6 and the food patch). The result is the
attached patch which could be commited to trunk, if nobody
has any objection or problems in the code are found.

Migration is a pretty radical game concept and changes the
game mechanics substantially. There are similarities between
certain aspects of this feature and other civ game rules,
for example the "city migration score" is sort of like a
city's "culture rating" from civ3; the modular nature of the
code in this patch will make future generalizations to
features like "culture" possible.

The main change from v6 is in the naming of the various
migration settings. There is now one master setting 'migration'
which controls whether migration is activated in the game.
The other settings have no effect if this option is disabled.
By default 'migration' is set to 0, i.e. migration is off.

The other migration settings are renamed:
  migrationturn   - mgr_turninterval
  migrationfood   - mgr_foodneeded
  migrationdist   - mgr_distance
  migrationworld  - mgr_worldchance
  migrationplayer - mgr_nationchance

This is so that users do not have to type the full "migration"
prefix to get an unambiguous match, yet still can see from
the name that the setting is migration related.

The patch in 40671 is required to make the set command
work with option names containing underscores.

In anticipation of future use beyond the migration feature,
I have made the city "migration score" a field in the city
struct, and added this field to the city info packet sent
to clients which can see the city internals. However, since
at the moment the score is only updated and used in the
check_city_migrations() function, and since there would
appear to be no secfile functions for float datatypes, I
have neglected to save/load this field in savegames. This
can be easily added later if needed.

As for user visible messages, I have changed the migration
messages to give the nation of a city rather than the name
of the player that owns it. I have also added TRANS
(translator) comments to clarify what the %s formats should
be interpreted as.

Otherwise I have just normalized and simplified the code
in a few places. The logic should be same as in v6.

I would appreciate more testing (if you don't mind doing
the full re-compile when this patch is applied), especially
gameplay with various combinations of the migration settings
to determine the best default values.


-----------------------------------------------------------------------
旅は長くて苦しかった。たくさん死んじゃった。
 common/city.h      |    1 +
 common/game.c      |    6 +
 common/game.h      |   20 +++
 common/packets.def |    7 +
 server/citytools.c |    7 +-
 server/citytools.h |    8 +-
 server/cityturn.c  |  397 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 server/cityturn.h  |    3 +
 server/savegame.c  |   30 ++++
 server/settings.c  |   78 ++++++++++
 server/srv_main.c  |    7 +
 version.in         |    2 +-
 12 files changed, 559 insertions(+), 7 deletions(-)

diff --git a/common/city.h b/common/city.h
index 6fef23f..f3f36cf 100644
--- a/common/city.h
+++ b/common/city.h
@@ -330,6 +330,7 @@ struct city {
   int steal;                    /* diplomats steal once; for spies, gets harder */
   int turn_founded;
   int turn_last_built;
+  float migration_score;        /* Updated by check_city_migrations. */
 
   int before_change_shields;    /* If changed this turn, shields before penalty */
   int caravan_shields;          /* If caravan has helped city to build wonder. */
diff --git a/common/game.c b/common/game.c
index 25e627f..31c7337 100644
--- a/common/game.c
+++ b/common/game.c
@@ -275,6 +275,12 @@ void game_init(void)
   game.info.celebratesize = GAME_DEFAULT_CELEBRATESIZE;
   game.info.savepalace    = GAME_DEFAULT_SAVEPALACE;
   game.info.natural_city_names = GAME_DEFAULT_NATURALCITYNAMES;
+  game.info.migration        = GAME_DEFAULT_MIGRATION;
+  game.info.mgr_turninterval = GAME_DEFAULT_MGR_TURNINTERVAL;
+  game.info.mgr_foodneeded   = GAME_DEFAULT_MGR_FOODNEEDED;
+  game.info.mgr_distance     = GAME_DEFAULT_MGR_DISTANCE;
+  game.info.mgr_worldchance  = GAME_DEFAULT_MGR_WORLDCHANCE;
+  game.info.mgr_nationchance = GAME_DEFAULT_MGR_NATIONCHANCE;
   game.info.angrycitizen  = GAME_DEFAULT_ANGRYCITIZEN;
   game.info.foodbox       = GAME_DEFAULT_FOODBOX;
   game.info.shieldbox = GAME_DEFAULT_SHIELDBOX;
diff --git a/common/game.h b/common/game.h
index f993b36..ce4da03 100644
--- a/common/game.h
+++ b/common/game.h
@@ -250,6 +250,26 @@ bool setting_class_is_changeable(enum sset_class class);
 
 #define GAME_DEFAULT_NATURALCITYNAMES TRUE
 
+#define GAME_DEFAULT_MIGRATION        FALSE
+
+#define GAME_DEFAULT_MGR_TURNINTERVAL 5
+#define GAME_MIN_MGR_TURNINTERVAL     1
+#define GAME_MAX_MGR_TURNINTERVAL     100
+
+#define GAME_DEFAULT_MGR_FOODNEEDED   TRUE
+
+#define GAME_DEFAULT_MGR_DISTANCE     3
+#define GAME_MIN_MGR_DISTANCE         1
+#define GAME_MAX_MGR_DISTANCE         7
+
+#define GAME_DEFAULT_MGR_NATIONCHANCE 50
+#define GAME_MIN_MGR_NATIONCHANCE     1
+#define GAME_MAX_MGR_NATIONCHANCE     100
+
+#define GAME_DEFAULT_MGR_WORLDCHANCE  10
+#define GAME_MIN_MGR_WORLDCHANCE      1
+#define GAME_MAX_MGR_WORLDCHANCE      100
+
 #define GAME_DEFAULT_AQUEDUCTLOSS    0
 #define GAME_MIN_AQUEDUCTLOSS        0
 #define GAME_MAX_AQUEDUCTLOSS        100
diff --git a/common/packets.def b/common/packets.def
index 9f2b173..57883fc 100644
--- a/common/packets.def
+++ b/common/packets.def
@@ -414,6 +414,12 @@ PACKET_GAME_INFO=15; sc
   UINT8 razechance;
   BOOL savepalace;
   BOOL natural_city_names;
+  BOOL migration;
+  UINT8 mgr_turninterval;
+  BOOL mgr_foodneeded;
+  UINT8 mgr_distance;
+  UINT8 mgr_nationchance;
+  UINT8 mgr_worldchance;
   BOOL turnblock;
   BOOL fixedlength;
   BOOL auto_ai_toggle;
@@ -535,6 +541,7 @@ PACKET_CITY_INFO=21; sc,lsend
 
   TURN turn_founded;
   TURN turn_last_built;
+  FLOAT migration_score;
   UINT8 changed_from_kind;
   UINT8 changed_from_value;
   UINT16 before_change_shields;
diff --git a/server/citytools.c b/server/citytools.c
index 529a03c..f6f121f 100644
--- a/server/citytools.c
+++ b/server/citytools.c
@@ -703,8 +703,10 @@ If sea_required, returned city must be adjacent to ocean.
 If pexclcity, do not return it as the closest city.
 Returns NULL if no satisfactory city can be found.
 ***********************************************************************/
-struct city *find_closest_owned_city(struct player *pplayer, struct tile *ptile,
-				     bool sea_required, struct city *pexclcity)
+struct city *find_closest_owned_city(const struct player *pplayer,
+                                     const struct tile *ptile,
+                                     bool sea_required,
+                                     const struct city *pexclcity)
 {
   int dist = -1;
   struct city *rcity = NULL;
@@ -1812,6 +1814,7 @@ void package_city(struct city *pcity, struct packet_city_info *packet,
 
   packet->turn_last_built=pcity->turn_last_built;
   packet->turn_founded = pcity->turn_founded;
+  packet->migration_score = pcity->migration_score;
 
   packet->changed_from_kind = pcity->changed_from.kind;
   packet->changed_from_value = universal_number(&pcity->changed_from);
diff --git a/server/citytools.h b/server/citytools.h
index 2a475e1..2edf3ab 100644
--- a/server/citytools.h
+++ b/server/citytools.h
@@ -41,10 +41,10 @@ void transfer_city_units(struct player *pplayer, struct player *pvictim,
 void transfer_city(struct player *ptaker, struct city *pcity,
 		   int kill_outside, bool transfer_unit_verbose,
 		   bool resolve_stack, bool raze);
-struct city *find_closest_owned_city(struct player *pplayer,
-				     struct tile *ptile,
-				     bool sea_required,
-				     struct city *pexclcity);
+struct city *find_closest_owned_city(const struct player *pplayer,
+                                     const struct tile *ptile,
+                                     bool sea_required,
+                                     const struct city *pexclcity);
 void unit_enter_city(struct unit *punit, struct city *pcity, bool passenger);
 
 bool send_city_suppression(bool now);
diff --git a/server/cityturn.c b/server/cityturn.c
index f56026e..d3230e4 100644
--- a/server/cityturn.c
+++ b/server/cityturn.c
@@ -90,6 +90,10 @@ static void define_orig_production_values(struct city *pcity);
 static void update_city_activity(struct player *pplayer, struct city *pcity);
 static void nullify_caravan_and_disband_plus(struct city *pcity);
 
+static float city_migration_score(const struct city *pcity);
+static bool do_city_migration(struct city *pcity_from,
+                              struct city *pcity_to);
+
 /**************************************************************************
 ...
 **************************************************************************/
@@ -1867,3 +1871,396 @@ static bool disband_city(struct city *pcity)
   remove_city(pcity);
   return TRUE;
 }
+
+/***************************************************************************
+  Helper function to calculate a "score" of a city. The score is used to get
+  an estimate of the "migration desirability" of the city. The higher the
+  score the more likely citizens will migrate to it.
+
+  The score depends on the city size, the feeling of its citizens, the cost
+  of all buildings in the city, and the surplus of trade, luxury and
+  science.
+
+  formula:
+    score = ([city size] + feeling) * factors
+
+  * feeling of the citizens
+    feeling = 1.00 * happy citizens
+            + 0.00 * content citizens
+            - 0.25 * unhappy citizens
+            - 0.50 * unhappy citizens
+
+  * factors
+    * the build costs of all buildings
+      f = (1 + (1 - exp(-[build shield cost]/1000))/5)
+    * the trade of the city
+      f = (1 + (1 - exp(-[city surplus trade]/100))/5)
+    * the luxury within the city
+      f = (1 + (1 - exp(-[city surplus luxury]/100))/5)
+    * the science within the city
+      f = (1 + (1 - exp(-[city surplus science]/100))/5)
+
+  all factors f have values between 1 and 1.2; the overall factor will be
+  between 1.0 (smaller cities) and 2.0 (bigger cities)
+
+  [build shield cost], [city surplus trade], [city surplus luxury] and
+  [city surplus science] _must_ be >= 0!
+
+  * if the city has at least one wonder a factor of 1.25 is added
+  * for the capital an additional factor of 1.25 is used
+**************************************************************************/
+static float city_migration_score(const struct city *pcity)
+{
+  float score = 0.0;
+  int build_shield_cost = 0;
+  bool has_wonder = FALSE;
+
+  if (!pcity) {
+    return 0.0;
+  }
+
+  /* feeling of the citizens */
+  score = (pcity->size + 1.00 * pcity->feel[CITIZEN_HAPPY][FEELING_FINAL]
+           + 0.00 * pcity->feel[CITIZEN_CONTENT][FEELING_FINAL]
+           - 0.25 * pcity->feel[CITIZEN_UNHAPPY][FEELING_FINAL]
+           - 0.50 * pcity->feel[CITIZEN_ANGRY][FEELING_FINAL]);
+
+  /* calculate shield build cost for all buildings */
+  city_built_iterate(pcity, pimprove) {
+    build_shield_cost += impr_build_shield_cost(pimprove);
+    if (is_wonder(pimprove)) {
+      /* this city has a wonder */
+      has_wonder = TRUE;
+    }
+  } city_built_iterate_end;
+
+  /* take shield costs of all buidings into account; normalized by 1000 */
+  score *= (1 + (1 - exp(- (float) build_shield_cost / 1000)) / 5);
+  /* take trade into account; normalized by 100 */
+  score *= (1 + (1 - exp(- (float) pcity->surplus[O_TRADE] / 100)) / 5);
+  /* take luxury into account; normalized by 100 */
+  score *= (1 + (1 - exp(- (float) pcity->surplus[O_LUXURY] / 100)) / 5);
+  /* take science into account; normalized by 100 */
+  score *= (1 + (1 - exp(- (float) pcity->surplus[O_SCIENCE] / 100)) / 5);
+
+  if (has_wonder) {
+    /* people like wonders */
+    score *= 1.25;
+  }
+
+  if (is_capital(pcity)) {
+    /* the capital is a magnet for the citizens */
+    score *= 1.25;
+  }
+
+  freelog(LOG_DEBUG, "[M] %s score: %.3f", city_name(pcity), score);
+
+  return score;
+}
+
+/**************************************************************************
+  Do the migrations between the cities that overlap, if the growth of the
+  target city is not blocked due to a missing improvement or missing food.
+
+  Returns TRUE if migration occured.
+**************************************************************************/
+static bool do_city_migration(struct city *pcity_from,
+                              struct city *pcity_to)
+{
+  struct player *pplayer_from, *pplayer_to;
+  struct tile *ptile_from, *ptile_to;
+  const char *name_from, *name_to, *nation_from, *nation_to;
+  char saved_name_from[MAX_LEN_NAME];
+  struct city *rcity = NULL;
+
+  if (!pcity_from || !pcity_to) {
+    return FALSE;
+  }
+
+  pplayer_from = city_owner(pcity_from);
+  pplayer_to = city_owner(pcity_to);
+  name_from = city_name(pcity_from);
+  name_to = city_name(pcity_to);
+  nation_from = nation_adjective_for_player(pplayer_from);
+  nation_to = nation_adjective_for_player(pplayer_to);
+  ptile_from = city_tile(pcity_from);
+  ptile_to = city_tile(pcity_to);
+
+  if (game.info.mgr_foodneeded && pcity_to->surplus[O_FOOD] < 0) {
+    /* insufficiency food in receiver city; no additional citizens */
+    if (pplayer_from == pplayer_to) {
+      /* migration between one nation */
+      /* TRANS: From <city1> to <city2>. */
+      notify_player(pplayer_to, ptile_to, E_CITY_TRANSFER,
+                    _("Migrants from %s can't go to %s because there is "
+                      "not enough food available!"),
+                    name_from, name_to);
+    } else {
+      /* migration between different nations */
+      /* TRANS: From <city1> to <city2> (<city2 nation adjective>). */
+      notify_player(pplayer_from, ptile_to, E_CITY_TRANSFER,
+                    _("Migrants from %s can't go to %s (%s) because there "
+                      "is not enough food available!"),
+                    name_from, name_to, nation_to);
+      /* TRANS: From <city1> (<city1 nation afjective>) to <city2>. */
+      notify_player(pplayer_to, ptile_to, E_CITY_TRANSFER,
+                    _("Migrants from %s (%s) can't go to %s because there "
+                      "is not enough food available!"),
+                    name_from, nation_from, name_to);
+    }
+
+    return FALSE;
+  }
+
+  if (!city_can_grow_to(pcity_to, pcity_to->size + 1)) {
+    /* receiver city can't grow  */
+    if (pplayer_from == pplayer_to) {
+      /* migration between one nation */
+      /* TRANS: From <city1> to <city2>. */
+      notify_player(pplayer_to, ptile_to, E_CITY_TRANSFER,
+                    _("Migrants from %s can't go to %s because it needs "
+                      "an improvement to grow!"),
+                    name_from, name_to);
+    } else {
+      /* migration between different nations */
+      /* TRANS: From <city1> to <city2> of <city2 nation adjective>. */
+      notify_player(pplayer_from, ptile_to, E_CITY_TRANSFER,
+                    _("Migrants from %s can't go to %s (%s) because it "
+                      "needs an improvement to grow!"),
+                    name_from, name_to, nation_to);
+      /* TRANS: From <city1> (<city1 nation afjective>) to <city2>. */
+      notify_player(pplayer_to, ptile_to, E_CITY_TRANSFER,
+                    _("Migrants from %s (%s) can't go to %s because it "
+                      "needs an improvement to grow!"),
+                    name_from, nation_from, name_to);
+    }
+
+    return FALSE;
+  }
+
+  /* we copy the city name, since maybe we disband the city! */
+  sz_strlcpy(saved_name_from, name_from);
+  /* reduce size of giver */
+  if (pcity_from->size == 1) {
+    /* do not destroy wonders */
+    city_built_iterate(pcity_from, pimprove) {
+      if (is_wonder(pimprove)) {
+        return FALSE;
+      }
+    } city_built_iterate_end;
+
+    /* find closest city other of the same player than pcity_from */
+    rcity = find_closest_owned_city(pplayer_from, ptile_from,
+                                    FALSE, pcity_from);
+
+    if (rcity) {
+      /* transfer all units to the closest city */
+      transfer_city_units(pplayer_from, pplayer_from,
+                          pcity_from->units_supported, rcity, pcity_from,
+                          -1, TRUE);
+      remove_city(pcity_from);
+
+      notify_player(pplayer_from, ptile_from, E_CITY_LOST,
+                    _("%s was disbanded by its citizens."),
+                    saved_name_from);
+    } else {
+      /* it's the only city of the nation */
+      return FALSE;
+    }
+  } else {
+    city_reduce_size(pcity_from, 1, pplayer_from);
+    city_refresh_vision(pcity_from);
+    city_refresh(pcity_from);
+  }
+  /* raise size of receiver city */
+  city_increase_size(pcity_to);
+  city_refresh_vision(pcity_to);
+  city_refresh(pcity_to);
+
+  if (pplayer_from == pplayer_to) {
+    /* migration between one nation */
+    /* TRANS: From <city1> to <city2>. */
+    notify_player(pplayer_from, ptile_to, E_CITY_TRANSFER,
+                  _("Migrants from %s moved to %s in search of a better "
+                    "life."), saved_name_from, name_to);
+  } else {
+    /* migration between different nations */
+    /* TRANS: From <city1> to <city2> (<city2 nation adjective>). */
+    notify_player(pplayer_from, ptile_to, E_CITY_TRANSFER,
+                  _("Migrants from %s moved to %s (%s) in search of a "
+                    "better life."),
+                  saved_name_from, name_to, nation_to);
+    /* TRANS: From <city1> (<city1 nation adjective>) to <city2>. */
+    notify_player(pplayer_to, ptile_to, E_CITY_TRANSFER,
+                  _("Migrants from %s (%s) moved to %s in search of a "
+                    "better life."),
+                  saved_name_from, nation_from, name_to);
+  }
+
+  freelog(LOG_DEBUG, "[M] T%d migration successful (%s -> %s)",
+          game.info.turn, saved_name_from, name_to);
+
+  return TRUE;
+}
+
+/**************************************************************************
+  Check for citizens who want to migrate between the cities that overlap.
+  Migrants go to the city with higher score, if the growth of the target
+  city is not blocked due to a missing improvement.
+
+  The following setting are used:
+
+  'game.info.mgr_turninterval' controls the number of turns between
+  migration checks for one city (counted from the founding). If this
+  setting is zero, or it is the first turn (T0), migration does no occur.
+
+  'game.info.mgr_distance' is the maximal distance for migration.
+
+  'game.info.mgr_nationchance' gives the chance for migration within one
+  nation.
+
+  'game.info.mgr_worldchance' gives the chance for migration between all
+  nations.
+**************************************************************************/
+void check_city_migrations(struct player *pplayer)
+{
+  float best_city_player_score, best_city_world_score;
+  struct city *best_city_player, *best_city_world, *acity;
+  float score_from, score_tmp, weight;
+  int dist;
+
+  if (!game.info.migration) {
+    return;
+  }
+
+  if (!pplayer || !pplayer->cities) {
+    return;
+  }
+
+  if (game.info.mgr_turninterval <= 0
+      || (game.info.mgr_worldchance <= 0
+          && game.info.mgr_nationchance <= 0)) {
+    return;
+  }
+
+  city_list_iterate(pplayer->cities, pcity) {
+    pcity->migration_score = city_migration_score(pcity);
+  } city_list_iterate_end;
+
+  /* check for each city
+   * city_list_iterate_safe_end must be used because we could
+   * remove one city from the list */
+  city_list_iterate_safe(pplayer->cities, pcity) {
+    /* no migration out of the capital */
+    if (is_capital(pcity)) {
+      continue;
+    }
+
+    /* check only each (game.info.mgr_turninterval) turn
+     * (counted from the funding turn) and do not migrate
+     * the same turn a city is founded */
+    if (game.info.turn == pcity->turn_founded
+        || ((game.info.turn - pcity->turn_founded)
+            % game.info.mgr_turninterval) != 0) {
+      continue;
+    }
+
+    best_city_player_score = 0.0;
+    best_city_world_score = 0.0;
+    best_city_player = NULL;
+    best_city_world = NULL;
+
+    /* score of the actual city
+     * taking into account a persistence factor of 3 */
+    score_from = pcity->migration_score * 3;
+
+    freelog(LOG_DEBUG, "[M] T%d check city: %s score: %6.3f (%s)",
+            game.info.turn, city_name(pcity), score_from,
+            player_name(pplayer));
+
+    /* consider all cities within the set distance */
+    iterate_outward(city_tile(pcity), game.info.mgr_distance + 1, ptile) {
+      acity = tile_city(ptile);
+
+      if (!acity || acity == pcity) {
+        /* no city or the city in the center */
+        continue;
+      }
+
+      /* distance between the two cities */
+      dist = real_map_distance(city_tile(pcity), city_tile(acity));
+
+      /* score of the second city, weighted by the distance */
+      weight = ((float) (GAME_MAX_MGR_DISTANCE + 1 - dist)
+                / (float) (GAME_MAX_MGR_DISTANCE + 1));
+      score_tmp = acity->migration_score * weight;
+
+      freelog(LOG_DEBUG, "[M] T%d - compare city: %s (%s) dist: %d "
+              "score: %6.3f", game.info.turn, city_name(acity),
+              player_name(city_owner(acity)), dist, score_tmp);
+
+      if (game.info.mgr_nationchance > 0 && city_owner(acity) == pplayer) {
+        /* migration between cities of the same owner */
+        if (score_tmp > score_from && score_tmp > best_city_player_score) {
+          /* select the best! */
+          best_city_player_score = score_tmp;
+          best_city_player = acity;
+
+          freelog(LOG_DEBUG, "[M] T%d - best city (player): %s (%s) score: "
+                  "%6.3f (> %6.3f)", game.info.turn,
+                  city_name(best_city_player), player_name(pplayer),
+                  best_city_player_score, score_from);
+        }
+      } else if (game.info.mgr_worldchance > 0
+                 && city_owner(acity) != pplayer) {
+        /* migration between cities of different owners */
+        if (score_tmp > score_from && score_tmp > best_city_world_score) {
+          /* select the best! */
+          best_city_world_score = score_tmp;
+          best_city_world = acity;
+
+          freelog(LOG_DEBUG, "[M] T%d - best city (world): %s (%s) score: "
+                  "%6.3f (> %6.3f)", game.info.turn,
+                  city_name(best_city_world),
+                  player_name(city_owner(best_city_world)),
+                  best_city_world_score, score_from);
+        }
+      }
+    } iterate_outward_end;
+
+    if (best_city_player_score > 0) {
+      /* first, do the migration within one nation */
+      if (myrand(100) >= game.info.mgr_nationchance) {
+        /* no migration */
+        notify_player(pplayer, city_tile(pcity), E_CITY_TRANSFER,
+                      _("Citizens of %s are thinking about migrating to %s "
+                        "for a better life."),
+                      pcity->name, city_name(best_city_player));
+      } else {
+        do_city_migration(pcity, best_city_player);
+      }
+
+      /* stop here */
+      continue;
+    }
+
+    if (best_city_world_score > 0) {
+      /* second, do the migration between all nations */
+      if (myrand(100) >= game.info.mgr_worldchance) {
+        const char *nname;
+        nname = nation_adjective_for_player(city_owner(best_city_world));
+        /* no migration */
+        /* TRANS: <city1> to <city2> (<city2 nation adjective>). */
+        notify_player(pplayer, city_tile(pcity), E_CITY_TRANSFER,
+                      _("Citizens of %s are thinking about migrating to %s "
+                        "(%s) for a better life."),
+                      city_name(pcity), city_name(best_city_world), nname);
+      } else {
+        do_city_migration(pcity, best_city_world);
+      }
+
+      /* stop here */
+      continue;
+    }
+  } city_list_iterate_safe_end;
+}
diff --git a/server/cityturn.h b/server/cityturn.h
index d4b69e2..25b3196 100644
--- a/server/cityturn.h
+++ b/server/cityturn.h
@@ -46,4 +46,7 @@ void remove_obsolete_buildings(struct player *pplayer);
 void advisor_choose_build(struct player *pplayer, struct city *pcity);
 
 void nullify_prechange_production(struct city *pcity);
+
+void check_city_migrations(struct player *pplayer);
+
 #endif  /* FC__CITYTURN_H */
diff --git a/server/savegame.c b/server/savegame.c
index 1e79b76..82f3641 100644
--- a/server/savegame.c
+++ b/server/savegame.c
@@ -4354,6 +4354,24 @@ static void game_load_internal(struct section_file *file)
     game.info.allowed_city_names =
       secfile_lookup_int_default(file, game.info.allowed_city_names,
                                  "game.allowed_city_names"); 
+    game.info.migration =
+      secfile_lookup_int_default(file, game.info.migration,
+                                 "game.migration");
+    game.info.mgr_turninterval =
+      secfile_lookup_int_default(file, game.info.mgr_turninterval,
+                                 "game.mgr_turninterval");
+    game.info.mgr_foodneeded =
+      secfile_lookup_bool_default(file, game.info.mgr_foodneeded,
+                                 "game.mgr_foodneeded");
+    game.info.mgr_distance =
+      secfile_lookup_int_default(file, game.info.mgr_distance,
+                                 "game.mgr_distance");
+    game.info.mgr_nationchance =
+      secfile_lookup_int_default(file, game.info.mgr_nationchance,
+                                 "game.mgr_nationchance");
+    game.info.mgr_worldchance =
+      secfile_lookup_int_default(file, game.info.mgr_worldchance,
+                                 "game.mgr_worldchance");
 
     if(civstyle == 1) {
       string = "civ1";
@@ -5006,6 +5024,18 @@ void game_save(struct section_file *file, const char *save_reason)
   secfile_insert_bool(file, game.info.happyborders, "game.happyborders");
   secfile_insert_int(file, game.info.diplomacy, "game.diplomacy");
   secfile_insert_int(file, game.info.allowed_city_names, "game.allowed_city_names");
+  secfile_insert_bool(file, game.info.migration,
+                      "game.migration");
+  secfile_insert_int(file, game.info.mgr_turninterval,
+                     "game.mgr_turninterval");
+  secfile_insert_bool(file, game.info.mgr_foodneeded,
+                      "game.mgr_foodneeded");
+  secfile_insert_int(file, game.info.mgr_distance,
+                     "game.mgr_distance");
+  secfile_insert_int(file, game.info.mgr_nationchance,
+                     "game.mgr_nationchance");
+  secfile_insert_int(file, game.info.mgr_worldchance,
+                     "game.mgr_worldchance");
 
   {
     /* Now always save these, so the server options reflect the
diff --git a/server/settings.c b/server/settings.c
index 6612c2a..e714d4a 100644
--- a/server/settings.c
+++ b/server/settings.c
@@ -840,6 +840,84 @@ struct settings_s settings[] = {
               "on the surrounding terrain."),
            NULL, GAME_DEFAULT_NATURALCITYNAMES)
 
+  GEN_BOOL("migration", game.info.migration,
+           SSET_RULES_FLEXIBLE, SSET_SOCIOLOGY, SSET_RARE, SSET_TO_CLIENT,
+           N_("Whether to enable citizen migration"),
+           N_("This is the master setting that controls whether citizen "
+              "migration is active in the game. If enabled, citizens may "
+              "automatically move from less desirable cities to more "
+              "desirable ones. The \"desirability\" of a given city is "
+              "calculated from a number of factors. In general larger "
+              "cities with more income and improvements will be preferred. "
+              "Citizens will never migrate out of the capital, or cause "
+              "a wonder to be lost by disbanding a city. A number of other "
+              "settings control how migration behaves:\n"
+              "  mgr_turninterval - How often citizens try to migrate.\n"
+              "  mgr_foodneeded   - Whether destination food is checked.\n"
+              "  mgr_distance     - How far citizens will migrate.\n"
+              "  mgr_worldchance  - Chance for inter-nation migration.\n"
+              "  mgr_nationchance - Chance for intra-nation migration."),
+           NULL, GAME_DEFAULT_MIGRATION)
+
+  GEN_INT("mgr_turninterval", game.info.mgr_turninterval,
+          SSET_RULES_FLEXIBLE, SSET_SOCIOLOGY, SSET_RARE, SSET_TO_CLIENT,
+          N_("Number of turns between migrations from a city"),
+          N_("This setting controls the number of turns between migration "
+             "checks for a given city. The interval is calculated from "
+             "the founding turn of the city. So for example if this "
+             "setting is 5, citizens will look for a suitable migration "
+             "destination every five turns from the founding of their "
+             "current city. Migration will never occur the same turn "
+             "that a city is built. This setting has no effect unless "
+             "migration is enabled by the 'migration' setting."), NULL,
+          GAME_MIN_MGR_TURNINTERVAL, GAME_MAX_MGR_TURNINTERVAL,
+          GAME_DEFAULT_MGR_TURNINTERVAL)
+
+  GEN_BOOL("mgr_foodneeded", game.info.mgr_foodneeded,
+          SSET_RULES_FLEXIBLE, SSET_SOCIOLOGY, SSET_RARE, SSET_TO_CLIENT,
+           N_("Whether migration is limited by food"),
+           N_("If this setting is enabled, citizens will not migrate to "
+              "cities which would not have enough food to support them. "
+              "This setting has no effect unless migration is enabled by "
+              "the 'migration' setting."), NULL,
+           GAME_DEFAULT_MGR_FOODNEEDED)
+
+  GEN_INT("mgr_distance", game.info.mgr_distance,
+          SSET_RULES_FLEXIBLE, SSET_SOCIOLOGY, SSET_RARE, SSET_TO_CLIENT,
+          N_("Maximum distance citizens may migrate"),
+          N_("This setting controls how far citizens may look for a "
+             "suitable migration destination when deciding which city "
+             "to migrate to. For example, with a value of 3, there can "
+             "be at most a 2 tile distance between cities undergoing "
+             "migration. This setting has no effect unless migration "
+             "is activated by the 'migration' setting."), NULL,
+          GAME_MIN_MGR_DISTANCE, GAME_MAX_MGR_DISTANCE,
+          GAME_DEFAULT_MGR_DISTANCE)
+
+  GEN_INT("mgr_nationchance", game.info.mgr_nationchance,
+          SSET_RULES_FLEXIBLE, SSET_SOCIOLOGY, SSET_RARE, SSET_TO_CLIENT,
+          N_("Percent probability for migration within the same nation"),
+          N_("This setting controls how likely it is for citizens to "
+             "migrate between cities owned by the same player. Zero "
+             "indicates migration will never occur, 100 means that "
+             "migration will always occur if the citizens find a suitable "
+             "destination. This setting has no effect unless migration "
+             "is activated by the 'migration' setting."), NULL,
+          GAME_MIN_MGR_NATIONCHANCE, GAME_MAX_MGR_NATIONCHANCE,
+          GAME_DEFAULT_MGR_NATIONCHANCE)
+
+  GEN_INT("mgr_worldchance", game.info.mgr_worldchance,
+          SSET_RULES_FLEXIBLE, SSET_SOCIOLOGY, SSET_RARE, SSET_TO_CLIENT,
+          N_("Percent probability for migration between foreign cities"),
+          N_("This setting controls how likely it is for migration "
+             "to occur between cities owned by different players. "
+             "Zero indicates migration will never occur, 100 means "
+             "that citizens will always migrate if they find a suitable "
+             "destination. This setting has no effect if migration is "
+             "not enabled by the 'migration' setting."), NULL,
+          GAME_MIN_MGR_WORLDCHANCE, GAME_MAX_MGR_WORLDCHANCE,
+          GAME_DEFAULT_MGR_WORLDCHANCE)
+
   /* Meta options: these don't affect the internal rules of the game, but
    * do affect players.  Also options which only produce extra server
    * "output" and don't affect the actual game.
diff --git a/server/srv_main.c b/server/srv_main.c
index 8a97fce..dbf1464 100644
--- a/server/srv_main.c
+++ b/server/srv_main.c
@@ -877,6 +877,13 @@ static void end_turn(void)
   summon_barbarians(); /* wild guess really, no idea where to put it, but
 			  I want to give them chance to move their units */
 
+  if (game.info.migration) {
+    freelog(LOG_DEBUG, "Season of migrations");
+    players_iterate(pplayer) {
+      check_city_migrations(pplayer);
+    } players_iterate_end;
+  }
+
   update_environmental_upset(S_POLLUTION, &game.info.heating,
 			     &game.info.globalwarming, &game.info.warminglevel,
 			     global_warming);
diff --git a/version.in b/version.in
index 60968d7..bca9c61 100644
--- a/version.in
+++ b/version.in
@@ -23,5 +23,5 @@ RELEASE_MONTH=3
 #   - Avoid adding a new mandatory capability to the development branch for
 #     as long as possible.  We want to maintain network compatibility with
 #     the stable branch for as long as possible.
-NETWORK_CAPSTRING_MANDATORY="+Freeciv.Devel.2009.Jan.19"
+NETWORK_CAPSTRING_MANDATORY="+Freeciv.Devel.2009.Jan.21"
 NETWORK_CAPSTRING_OPTIONAL=""
_______________________________________________
Freeciv-dev mailing list
Freeciv-dev@gna.org
https://mail.gna.org/listinfo/freeciv-dev

Reply via email to