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

When an alliance is cancelled the server tries to resolve unit
stacks by teleporting "illegal" units to the nearest city of
their owner. By cleverly arranging stacks of units and swapping
key cities, two players can thus purposely teleport units a
theoretically infinite distance (by chaining multiple teleports)
so long as the units have moves left and diplomacy allows
alliances to be continually reformed. This kind of abuse of an
unintended "feature" is generally not appreciated by players
(e.g. see discussion on the longturn site).

The attached patch changes the function bounce_unit() to move a
unit to a random tile a maximum of two tiles away from its
current position, chosen from a list of tiles where the unit
can survive (can_unit_survive_at_tile()) and that is not
currently occupied by a non-ally unit or city. If no such tiles
exist, then the unit is disbanded.


-----------------------------------------------------------------------
魔導士は暴露に感謝しなかった。
diff --git a/server/unittools.c b/server/unittools.c
index cf4263f..6748158 100644
--- a/server/unittools.c
+++ b/server/unittools.c
@@ -1114,25 +1114,56 @@ bool teleport_unit_to_city(struct unit *punit, struct city *pcity,
 }
 
 /**************************************************************************
-  Teleport or remove a unit due to stack conflict.
+  Move or remove a unit due to stack conflicts. This function will try to
+  find a random safe tile within a two tile distance of the unit's current
+  tile and move the unit there. If no tiles are found, the unit is
+  disbanded. If 'verbose' is TRUE, a message is sent to the unit owner
+  regarding what happened.
 **************************************************************************/
 void bounce_unit(struct unit *punit, bool verbose)
 {
-  struct player *pplayer = unit_owner(punit);
-  struct city *pcity = find_closest_owned_city(pplayer, punit->tile,
-                                               is_sailing_unit(punit), NULL);
+  struct player *pplayer;
+  struct tile *punit_tile;
+  const int DIST = 2;
+  struct tile *tiles[4 * DIST * DIST + 4 * DIST + 1];
+  int count = 0;
+
+  if (!punit) {
+    return;
+  }
+
+  pplayer = unit_owner(punit);
+  punit_tile = unit_tile(punit);
+
+  square_iterate(punit_tile, DIST, ptile) {
+    if (count >= ARRAY_SIZE(tiles)) {
+      break;
+    }
+    if (can_unit_survive_at_tile(punit, ptile)
+        && !is_non_allied_city_tile(ptile, pplayer)
+        && !is_non_allied_unit_tile(ptile, pplayer)) {
+      tiles[count++] = ptile;
+    }
+  } square_iterate_end;
+
+  if (count > 0) {
+    struct tile *ptile = tiles[myrand(count)];
 
-  if (pcity && can_unit_exist_at_tile(punit, pcity->tile)) {
-    (void) teleport_unit_to_city(punit, pcity, 0, verbose);
-  } else {
-    /* remove it */
     if (verbose) {
-      notify_player(unit_owner(punit), punit->tile, E_UNIT_LOST_MISC,
-		       _("Disbanded your %s."),
-		       unit_name_translation(punit));
+      notify_player(pplayer, ptile, E_UNIT_RELOCATED, _("Moved your %s."),
+                    unit_name_translation(punit));
     }
-    wipe_unit(punit);
+    move_unit(punit, ptile, 0);
+    return;
+  }
+
+  /* Didn't find a place to bounce the unit, just disband it. */
+  if (verbose) {
+    notify_player(pplayer, punit_tile, E_UNIT_LOST_MISC,
+                  _("Disbanded your %s."),
+                  unit_name_translation(punit));
   }
+  wipe_unit(punit);
 }
 
 
diff --git a/server/unittools.c b/server/unittools.c
index 455a9bb..f8ebb99 100644
--- a/server/unittools.c
+++ b/server/unittools.c
@@ -1024,25 +1024,56 @@ bool teleport_unit_to_city(struct unit *punit, struct city *pcity,
 }
 
 /**************************************************************************
-  Teleport or remove a unit due to stack conflict.
+  Move or remove a unit due to stack conflicts. This function will try to
+  find a random safe tile within a two tile distance of the unit's current
+  tile and move the unit there. If no tiles are found, the unit is
+  disbanded. If 'verbose' is TRUE, a message is sent to the unit owner
+  regarding what happened.
 **************************************************************************/
 void bounce_unit(struct unit *punit, bool verbose)
 {
-  struct player *pplayer = unit_owner(punit);
-  struct city *pcity = find_closest_owned_city(pplayer, punit->tile,
-                                               is_sailing_unit(punit), NULL);
+  struct player *pplayer;
+  struct tile *punit_tile;
+  const int DIST = 2;
+  struct tile *tiles[4 * DIST * DIST + 4 * DIST + 1];
+  int count = 0;
+
+  if (!punit) {
+    return;
+  }
+
+  pplayer = unit_owner(punit);
+  punit_tile = punit->tile;
+
+  square_iterate(punit_tile, DIST, ptile) {
+    if (count >= ARRAY_SIZE(tiles)) {
+      break;
+    }
+    if (can_unit_survive_at_tile(punit, ptile)
+        && !is_non_allied_city_tile(ptile, pplayer)
+        && !is_non_allied_unit_tile(ptile, pplayer)) {
+      tiles[count++] = ptile;
+    }
+  } square_iterate_end;
+
+  if (count > 0) {
+    struct tile *ptile = tiles[myrand(count)];
 
-  if (pcity && can_unit_exist_at_tile(punit, pcity->tile)) {
-    (void) teleport_unit_to_city(punit, pcity, 0, verbose);
-  } else {
-    /* remove it */
     if (verbose) {
-      notify_player(unit_owner(punit), punit->tile, E_UNIT_LOST,
-		       _("Disbanded your %s."),
-		       unit_name_translation(punit));
+      notify_player(pplayer, ptile, E_UNIT_RELOCATED, _("Moved your %s."),
+                    unit_name_translation(punit));
     }
-    wipe_unit(punit);
+    move_unit(punit, ptile, 0);
+    return;
+  }
+
+  /* Didn't find a place to bounce the unit, just disband it. */
+  if (verbose) {
+    notify_player(pplayer, punit_tile, E_UNIT_LOST,
+                  _("Disbanded your %s."),
+                  unit_name_translation(punit));
   }
+  wipe_unit(punit);
 }
 
 
_______________________________________________
Freeciv-dev mailing list
Freeciv-dev@gna.org
https://mail.gna.org/listinfo/freeciv-dev

Reply via email to