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