Author: sveinung
Date: Mon Aug 10 16:06:25 2015
New Revision: 29437

URL: http://svn.gna.org/viewcvs/freeciv?rev=29437&view=rev
Log:
Don't break unique unit rules via unit transfer

See bug #23758

Modified:
    branches/S2_5/common/unittype.c
    branches/S2_5/common/unittype.h
    branches/S2_5/server/citytools.c
    branches/S2_5/server/diplomats.c
    branches/S2_5/server/unithand.c
    branches/S2_5/server/unittools.c

Modified: branches/S2_5/common/unittype.c
URL: 
http://svn.gna.org/viewcvs/freeciv/branches/S2_5/common/unittype.c?rev=29437&r1=29436&r2=29437&view=diff
==============================================================================
--- branches/S2_5/common/unittype.c     (original)
+++ branches/S2_5/common/unittype.c     Mon Aug 10 16:06:25 2015
@@ -620,6 +620,31 @@
 }
 
 /**************************************************************************
+  Returns TRUE iff the unit type is unique and the player already has one.
+**************************************************************************/
+bool utype_player_already_has_this_unique(const struct player *pplayer,
+                                          const struct unit_type *putype)
+{
+  if (!utype_has_flag(putype, UTYF_UNIQUE)) {
+    /* This isn't a unique unit type. */
+    return FALSE;
+  }
+
+  unit_list_iterate(pplayer->units, existing_unit) {
+    if (putype == unit_type(existing_unit)) {
+      /* FIXME: This could be slow if we have lots of units. We could
+       * consider keeping an array of unittypes updated with this info
+       * instead. */
+
+      return TRUE;
+    }
+  } unit_list_iterate_end;
+
+  /* The player doesn't already have one. */
+  return FALSE;
+}
+
+/**************************************************************************
 Whether player can build given unit somewhere,
 ignoring whether unit is obsolete and assuming the
 player has a coastal city.
@@ -682,17 +707,11 @@
         return FALSE;
       }
     }
-    
-  }
-  if (utype_has_flag(punittype, UTYF_UNIQUE)) {
-    /* FIXME: This could be slow if we have lots of units. We could
-     * consider keeping an array of unittypes updated with this info 
-     * instead. */
-    unit_list_iterate(p->units, punit) {
-      if (unit_type(punit) == punittype) { 
-        return FALSE;
-      }
-    } unit_list_iterate_end;
+  }
+
+  if (utype_player_already_has_this_unique(p, punittype)) {
+    /* A player can only have one unit of each unique unit type. */
+    return FALSE;
   }
 
   /* If the unit has a building requirement, we check to see if the player

Modified: branches/S2_5/common/unittype.h
URL: 
http://svn.gna.org/viewcvs/freeciv/branches/S2_5/common/unittype.h?rev=29437&r1=29436&r2=29437&view=diff
==============================================================================
--- branches/S2_5/common/unittype.h     (original)
+++ branches/S2_5/common/unittype.h     Mon Aug 10 16:06:25 2015
@@ -378,6 +378,8 @@
     TYPED_LIST_ITERATE(struct combat_bonus, bonuslist, pbonus)
 #define combat_bonus_list_iterate_end LIST_ITERATE_END
 
+BV_DEFINE(bv_unit_types, U_LAST);
+
 struct veteran_level {
   struct name_translation name; /* level/rank name */
   int power_fact; /* combat/work speed/diplomatic power factor (in %) */
@@ -584,6 +586,9 @@
                       const struct unit_type *from,
                       const struct unit_type *to);
 
+bool utype_player_already_has_this_unique(const struct player *pplayer,
+                                          const struct unit_type *putype);
+
 bool can_player_build_unit_direct(const struct player *p,
                                  const struct unit_type *punittype);
 bool can_player_build_unit_later(const struct player *p,

Modified: branches/S2_5/server/citytools.c
URL: 
http://svn.gna.org/viewcvs/freeciv/branches/S2_5/server/citytools.c?rev=29437&r1=29436&r2=29437&view=diff
==============================================================================
--- branches/S2_5/server/citytools.c    (original)
+++ branches/S2_5/server/citytools.c    Mon Aug 10 16:06:25 2015
@@ -598,6 +598,24 @@
   } else {
     struct tile *utile = unit_tile(punit);
     struct city *in_city = tile_city(utile);
+
+    if (utype_player_already_has_this_unique(to_player,
+                                             unit_type(punit))) {
+      /* This is a unique unit that to_player already has. A transfer would
+       * break the rule that a player only may have one unit of each unique
+       * unit type. */
+
+      log_debug("%s already have a %s. Can't transfer from %s",
+                nation_rule_name(nation_of_player(to_player)),
+                unit_rule_name(punit),
+                nation_rule_name(nation_of_player(from_player)));
+
+      /* TODO: What should be done when the unit is a game loss unit? Maybe
+       * it should be bounced rather than killed? */
+      wipe_unit(punit, ULR_CITY_LOST, NULL);
+
+      return;
+    }
 
     if (in_city) {
       log_verbose("Transferred %s in %s from %s to %s",

Modified: branches/S2_5/server/diplomats.c
URL: 
http://svn.gna.org/viewcvs/freeciv/branches/S2_5/server/diplomats.c?rev=29437&r1=29436&r2=29437&view=diff
==============================================================================
--- branches/S2_5/server/diplomats.c    (original)
+++ branches/S2_5/server/diplomats.c    Mon Aug 10 16:06:25 2015
@@ -432,6 +432,15 @@
   uplayer = unit_owner(pvictim);
   /* We might make it allowable in peace with a loss of reputation */
   if (!uplayer || pplayers_allied(pplayer, uplayer)) {
+    return;
+  }
+
+  /* Sanity check: The victim isn't a unique unit the actor player already
+   * has. */
+  if (utype_player_already_has_this_unique(pplayer,
+                                           unit_type(pvictim))) {
+    log_debug("bribe-unit: already got unique unit");
+
     return;
   }
 

Modified: branches/S2_5/server/unithand.c
URL: 
http://svn.gna.org/viewcvs/freeciv/branches/S2_5/server/unithand.c?rev=29437&r1=29436&r2=29437&view=diff
==============================================================================
--- branches/S2_5/server/unithand.c     (original)
+++ branches/S2_5/server/unithand.c     Mon Aug 10 16:06:25 2015
@@ -366,6 +366,9 @@
 
   if (old_owner != new_owner) {
     struct city *pcity = tile_city(punit->tile);
+
+    fc_assert(!utype_player_already_has_this_unique(new_owner,
+                                                    unit_type(punit)));
 
     vision_clear_sight(punit->server.vision);
     vision_free(punit->server.vision);
@@ -1475,10 +1478,44 @@
 
     if (unit_has_type_flag(punit, UTYF_CAPTURER) && pcity == NULL) {
       bool capture_possible = TRUE;
-
+      bv_unit_types unique_on_tile;
+
+      BV_CLR_ALL(unique_on_tile);
       unit_list_iterate(pdesttile->units, to_capture) {
         if (!unit_has_type_flag(to_capture, UTYF_CAPTURABLE)) {
           capture_possible = FALSE;
+          break;
+        }
+
+        /* Sanity check: make sure that the capture won't result in the actor
+         * ending up with more than one unit of each unique unit type. */
+
+        /* Check what the player already has. */
+        if (utype_player_already_has_this_unique(pplayer,
+                                                 unit_type(to_capture))) {
+          /* The player already has a unit of this kind. */
+          capture_possible = FALSE;
+        }
+
+        if (utype_has_flag(unit_type(to_capture), UTYF_UNIQUE)) {
+          /* The type of the units at the tile must also be checked. Two allied
+       * players can both have their unique unit at the same tile.
+       * Capturing them both would give the actor two units of a kind that
+       * is supposed to be unique. */
+
+          if (BV_ISSET(unique_on_tile, utype_index(unit_type(to_capture)))) {
+            /* There is another unit of the same kind at this tile. */
+            capture_possible = FALSE;
+          } else {
+            /* Remember the unit type in case another unit of the same kind is
+         * encountered later. */
+            BV_SET(unique_on_tile, utype_index(unit_type(to_capture)));
+          }
+        }
+
+        if (!capture_possible) {
+          log_debug("capture units: already got unique unit");
+
           break;
         }
       } unit_list_iterate_end;

Modified: branches/S2_5/server/unittools.c
URL: 
http://svn.gna.org/viewcvs/freeciv/branches/S2_5/server/unittools.c?rev=29437&r1=29436&r2=29437&view=diff
==============================================================================
--- branches/S2_5/server/unittools.c    (original)
+++ branches/S2_5/server/unittools.c    Mon Aug 10 16:06:25 2015
@@ -1962,6 +1962,9 @@
                                int homecity, enum unit_loss_reason reason)
 {
   struct unit *gained_unit;
+
+  fc_assert(!utype_player_already_has_this_unique(pplayer,
+                                                  unit_type(punit)));
 
   /* Convert the unit to your cause. Fog is lifted in the create algorithm. */
   gained_unit = create_unit_full(pplayer, unit_tile(punit),


_______________________________________________
Freeciv-commits mailing list
Freeciv-commits@gna.org
https://mail.gna.org/listinfo/freeciv-commits

Reply via email to