<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