Author: sveinung
Date: Sun Aug  9 00:48:45 2015
New Revision: 29408

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

See bug #23758

Modified:
    trunk/common/actions.c
    trunk/common/unittype.c
    trunk/common/unittype.h
    trunk/server/citytools.c
    trunk/server/diplomats.c
    trunk/server/unithand.c
    trunk/server/unittools.c

Modified: trunk/common/actions.c
URL: 
http://svn.gna.org/viewcvs/freeciv/trunk/common/actions.c?rev=29408&r1=29407&r2=29408&view=diff
==============================================================================
--- trunk/common/actions.c      (original)
+++ trunk/common/actions.c      Sun Aug  9 00:48:45 2015
@@ -575,6 +575,22 @@
     }
   }
 
+  if (wanted_action == ACTION_CAPTURE_UNITS
+      || wanted_action == ACTION_SPY_BRIBE_UNIT) {
+    /* Why this is a hard requirement: Can't transfer a unique unit if the
+     * actor player already has one. */
+    /* Info leak: The actor player may not see all targets of Capture
+     * Units. */
+    if (utype_player_already_has_this_unique(actor_player,
+                                             target_unittype)) {
+      return FALSE;
+    }
+
+    /* FIXME: Capture Unit may want to look for more than one unique unit
+     * of the same kind at the target tile. Currently caught by sanity
+     * check in do_capture_units(). */
+  }
+
   if (wanted_action == ACTION_ESTABLISH_EMBASSY) {
     /* Why this is a hard requirement: There is currently no point in
      * establishing an embassy when a real embassy already exists.

Modified: trunk/common/unittype.c
URL: 
http://svn.gna.org/viewcvs/freeciv/trunk/common/unittype.c?rev=29408&r1=29407&r2=29408&view=diff
==============================================================================
--- trunk/common/unittype.c     (original)
+++ trunk/common/unittype.c     Sun Aug  9 00:48:45 2015
@@ -1008,6 +1008,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.
@@ -1072,17 +1097,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: trunk/common/unittype.h
URL: 
http://svn.gna.org/viewcvs/freeciv/trunk/common/unittype.h?rev=29408&r1=29407&r2=29408&view=diff
==============================================================================
--- trunk/common/unittype.h     (original)
+++ trunk/common/unittype.h     Sun Aug  9 00:48:45 2015
@@ -623,6 +623,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: trunk/server/citytools.c
URL: 
http://svn.gna.org/viewcvs/freeciv/trunk/server/citytools.c?rev=29408&r1=29407&r2=29408&view=diff
==============================================================================
--- trunk/server/citytools.c    (original)
+++ trunk/server/citytools.c    Sun Aug  9 00:48:45 2015
@@ -600,6 +600,33 @@
   } 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)));
+
+      if (verbose) {
+        notify_player(from_player, unit_tile(punit),
+                      E_UNIT_LOST_MISC, ftc_server,
+                      /* TRANS: Americans ... Leader */
+                      _("The %s already have a %s. Can't transfer yours."),
+                      nation_plural_for_player(to_player),
+                      unit_tile_link(punit));
+      }
+
+      /* 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: trunk/server/diplomats.c
URL: 
http://svn.gna.org/viewcvs/freeciv/trunk/server/diplomats.c?rev=29408&r1=29407&r2=29408&view=diff
==============================================================================
--- trunk/server/diplomats.c    (original)
+++ trunk/server/diplomats.c    Sun Aug  9 00:48:45 2015
@@ -476,6 +476,20 @@
 
   /* Sanity check: The actor still exists. */
   if (!pplayer || !pdiplomat || !unit_alive(pdiplomat->id)) {
+    return FALSE;
+  }
+
+  /* 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");
+    notify_player(pplayer, unit_tile(pdiplomat),
+                  E_UNIT_ILLEGAL_ACTION, ftc_server,
+                  /* TRANS: You already have a Leader. */
+                  _("You already have a %s."),
+                  unit_link(pvictim));
+
     return FALSE;
   }
 

Modified: trunk/server/unithand.c
URL: 
http://svn.gna.org/viewcvs/freeciv/trunk/server/unithand.c?rev=29408&r1=29407&r2=29408&view=diff
==============================================================================
--- trunk/server/unithand.c     (original)
+++ trunk/server/unithand.c     Sun Aug  9 00:48:45 2015
@@ -262,11 +262,52 @@
   struct city *pcity;
   char capturer_link[MAX_LEN_LINK];
   const char *capturer_nation = nation_plural_for_player(pplayer);
+  bv_unit_types unique_on_tile;
 
   /* Sanity check: The actor is still alive. */
   if (!unit_alive(punit->id)) {
     return FALSE;
   }
+
+  /* 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. */
+  BV_CLR_ALL(unique_on_tile);
+  unit_list_iterate(pdesttile->units, to_capture) {
+    bool unique_conflict = FALSE;
+
+    /* 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. */
+      unique_conflict = TRUE;
+    }
+
+    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. */
+        unique_conflict = TRUE;
+      } 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 (unique_conflict) {
+      log_debug("capture units: already got unique unit");
+      notify_player(pplayer, pdesttile, E_UNIT_ILLEGAL_ACTION, ftc_server,
+                    /* TRANS: You can only have one Leader. */
+                    _("You can only have one %s."),
+                    unit_link(to_capture));
+
+      return FALSE;
+    }
+  } unit_list_iterate_end;
 
   /* N.B: unit_link() always returns the same pointer. */
   sz_strlcpy(capturer_link, unit_link(punit));
@@ -1631,6 +1672,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);
 

Modified: trunk/server/unittools.c
URL: 
http://svn.gna.org/viewcvs/freeciv/trunk/server/unittools.c?rev=29408&r1=29407&r2=29408&view=diff
==============================================================================
--- trunk/server/unittools.c    (original)
+++ trunk/server/unittools.c    Sun Aug  9 00:48:45 2015
@@ -1938,6 +1938,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