From bc7d842ba437785e04b7b916469105f4381d5e29 Mon Sep 17 00:00:00 2001 From: Marko Lindqvist Date: Mon, 3 Apr 2023 17:39:08 +0300 Subject: [PATCH 27/27] Split actres parts from is_action_possible() New function actres_possible() See osdn #47752 Signed-off-by: Marko Lindqvist --- common/actions.c | 914 +---------------------------------------------- common/actres.c | 912 ++++++++++++++++++++++++++++++++++++++++++++++ common/actres.h | 9 + common/player.c | 11 + common/player.h | 2 + 5 files changed, 952 insertions(+), 896 deletions(-) diff --git a/common/actions.c b/common/actions.c index 8f506da0a2..cc63840795 100644 --- a/common/actions.c +++ b/common/actions.c @@ -33,7 +33,6 @@ #include "nation.h" #include "research.h" #include "server_settings.h" -#include "tile.h" #include "unit.h" /* Custom data types for obligatory hard action requirements. */ @@ -3020,25 +3019,6 @@ const char *action_enabler_vector_by_number_name(req_vec_num_in_item vec) } } -/**********************************************************************//** - Returns TRUE iff the specified player knows (has seen) the specified - tile. -**************************************************************************/ -static bool plr_knows_tile(const struct player *plr, - const struct tile *ttile) -{ - return plr && ttile && (tile_get_known(ttile, plr) != TILE_UNKNOWN); -} - -/**********************************************************************//** - Returns TRUE iff the specified player can see the specified tile. -**************************************************************************/ -static bool plr_sees_tile(const struct player *plr, - const struct tile *ttile) -{ - return plr && ttile && (tile_get_known(ttile, plr) == TILE_KNOWN_SEEN); -} - /**********************************************************************//** Returns the local building type of a city target. @@ -3732,10 +3712,7 @@ is_action_possible(const action_id wanted_action, const bool omniscient, const struct city *homecity) { - bool can_see_tgt_unit; - bool can_see_tgt_tile; enum fc_tristate out; - struct terrain *pterrain; struct action *paction = action_by_number(wanted_action); enum action_target_kind tkind = action_get_target_kind(paction); @@ -3755,19 +3732,6 @@ is_action_possible(const action_id wanted_action, || (tkind == ATK_SELF), "Missing target!"); - /* Only check requirement against the target unit if the actor can see it - * or if the evaluator is omniscient. The game checking the rules is - * omniscient. The player asking about their odds isn't. */ - can_see_tgt_unit = (omniscient || (target->unit - && can_player_see_unit(actor->player, - target->unit))); - - /* Only check requirement against the target tile if the actor can see it - * or if the evaluator is omniscient. The game checking the rules is - * omniscient. The player asking about their odds isn't. */ - can_see_tgt_tile = (omniscient - || plr_sees_tile(actor->player, target->tile)); - /* Info leak: The player knows where their unit is. */ if (tkind != ATK_SELF && !action_distance_accepted(paction, @@ -3828,236 +3792,35 @@ is_action_possible(const action_id wanted_action, return TRI_NO; } - /* Hard requirements for individual actions. */ - switch (paction->result) { - case ACTRES_CAPTURE_UNITS: - case ACTRES_SPY_BRIBE_UNIT: - /* Why this is a hard requirement: Can't transfer a unique unit if the - * actor player already has one. */ - /* Info leak: This is only checked for when the actor player can see - * the target unit. Since the target unit is seen its type is known. - * The fact that a city hiding the unseen unit is occupied is known. */ - - if (!can_see_tgt_unit) { - /* An omniscient player can see the target unit. */ - fc_assert(!omniscient); - - return TRI_MAYBE; - } - - if (utype_player_already_has_this_unique(actor->player, - target->unittype)) { - return TRI_NO; - } - - /* 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(). */ - - break; - - case ACTRES_SPY_TARGETED_STEAL_TECH: - /* Reason: The Freeciv code don't support selecting a target tech - * unless it is known that the victim player has it. */ - /* Info leak: The actor player knowns whose techs they can see. */ - if (!can_see_techs_of_target(actor->player, target->player)) { - return TRI_NO; - } - - break; - - case ACTRES_SPY_STEAL_GOLD: - /* If actor->unit can do the action the actor->player can see how much - * gold target->player have. Not requiring it is therefore pointless. - */ - if (target->player->economic.gold <= 0) { - return TRI_NO; - } - - break; - - case ACTRES_TRADE_ROUTE: - case ACTRES_MARKETPLACE: - { - /* Checked in action_hard_reqs_actor() */ - fc_assert_ret_val(homecity != NULL, TRI_NO); - - /* Can't establish a trade route or enter the market place if the - * cities can't trade at all. */ - /* TODO: Should this restriction (and the above restriction that the - * actor unit must have a home city) be kept for Enter Marketplace? */ - if (!can_cities_trade(homecity, target->city)) { - return TRI_NO; - } - - /* There are more restrictions on establishing a trade route than on - * entering the market place. */ - if (action_has_result(paction, ACTRES_TRADE_ROUTE) - && !can_establish_trade_route(homecity, target->city)) { - return TRI_NO; - } - } - - break; - - case ACTRES_HELP_WONDER: - case ACTRES_DISBAND_UNIT_RECOVER: - /* It is only possible to help the production if the production needs - * the help. (If not it would be possible to add shields for something - * that can't legally receive help if it is build later) */ - /* Info leak: The player knows that the production in their own city has - * been hurried (bought or helped). The information isn't revealed when - * asking for action probabilities since omniscient is FALSE. */ - if (!omniscient - && !can_player_see_city_internals(actor->player, target->city)) { - return TRI_MAYBE; - } - - if (!(target->city->shield_stock - < city_production_build_shield_cost(target->city))) { - return TRI_NO; - } - - break; - - case ACTRES_FOUND_CITY: - if (game.scenario.prevent_new_cities) { - /* Reason: allow scenarios to disable city founding. */ - /* Info leak: the setting is public knowledge. */ - return TRI_NO; - } - - if (can_see_tgt_tile && tile_city(target->tile)) { - /* Reason: a tile can have 0 or 1 cities. */ + /* Quick checks for action itself */ + if (paction->result == ACTRES_ATTACK + || paction->result == ACTRES_WIPE_UNITS) { + /* Reason: Keep the old rules. */ + if (!can_unit_attack_tile(actor->unit, paction, target->tile)) { return TRI_NO; } + } - if (citymindist_prevents_city_on_tile(target->tile)) { - if (omniscient) { - /* No need to check again. */ - return TRI_NO; - } else { - square_iterate(&(wld.map), target->tile, - game.info.citymindist - 1, otile) { - if (tile_city(otile) != NULL - && plr_sees_tile(actor->player, otile)) { - /* Known to be blocked by citymindist */ - return TRI_NO; - } - } square_iterate_end; - } - } - - /* The player may not have enough information to be certain. */ - - if (!can_see_tgt_tile) { - /* Need to know if target tile already has a city, has TER_NO_CITIES - * terrain, is non native to the actor or is owned by a foreigner. */ - return TRI_MAYBE; - } - - if (!omniscient) { - /* The player may not have enough information to find out if - * citymindist blocks or not. This doesn't depend on if it blocks. */ - square_iterate(&(wld.map), target->tile, - game.info.citymindist - 1, otile) { - if (!plr_sees_tile(actor->player, otile)) { - /* Could have a city that blocks via citymindist. Even if this - * tile has TER_NO_CITIES terrain the player don't know that it - * didn't change and had a city built on it. */ - return TRI_MAYBE; - } - } square_iterate_end; - } - - break; - - case ACTRES_JOIN_CITY: - { - int new_pop; - - if (!omniscient - && !player_can_see_city_externals(actor->player, target->city)) { - return TRI_MAYBE; - } - - new_pop = city_size_get(target->city) + unit_pop_value(actor->unit); - - if (new_pop > game.info.add_to_size_limit) { - /* Reason: Make the add_to_size_limit setting work. */ - return TRI_NO; - } - - if (!city_can_grow_to(target->city, new_pop)) { - /* Reason: respect city size limits. */ - /* Info leak: when it is legal to join a foreign city is legal and - * the EFT_SIZE_UNLIMIT effect or the EFT_SIZE_ADJ effect depends on - * something the actor player don't have access to. - * Example: depends on a building (like Aqueduct) that isn't - * VisibleByOthers. */ - return TRI_NO; - } - } + /* Hard requirements for results. */ + out = actres_possible(paction->result, actor, + target, target_extra, out, omniscient, + homecity); - break; + if (out == TRI_NO) { + /* Illegal because of a hard actor requirement. */ + return TRI_NO; + } - case ACTRES_NUKE_UNITS: + if (paction->result == ACTRES_NUKE_UNITS) { if (unit_attack_units_at_tile_result(actor->unit, paction, target->tile) != ATT_OK) { /* Unreachable. */ return TRI_NO; } - - break; - - case ACTRES_HOME_CITY: - /* Reason: can't change to what is. */ - /* Info leak: The player knows their unit's current home city. */ - if (homecity != NULL && homecity->id == target->city->id) { - /* This is already the unit's home city. */ - return TRI_NO; - } - - { - int slots = unit_type_get(actor->unit)->city_slots; - - if (slots > 0 && city_unit_slots_available(target->city) < slots) { - return TRI_NO; - } - } - - break; - - case ACTRES_UPGRADE_UNIT: - /* Reason: Keep the old rules. */ - /* Info leak: The player knows their unit's type. They know if they can - * build the unit type upgraded to. If the upgrade happens in a foreign - * city that fact may leak. This can be seen as a price for changing - * the rules to allow upgrading in a foreign city. - * The player knows how much gold they have. If the Upgrade_Price_Pct - * effect depends on information they don't have that information may - * leak. The player knows the location of their unit. They know if the - * tile has a city and if the unit can exist there outside a transport. - * The player knows their unit's cargo. By knowing the number and type - * of cargo, they can predict if there will be enough room in the unit - * upgraded to, as long as they know what unit type their unit will end - * up as. */ - if (unit_upgrade_test(actor->unit, FALSE) != UU_OK) { - return TRI_NO; - } - - break; - - case ACTRES_PARADROP: - case ACTRES_PARADROP_CONQUER: - /* Reason: Keep the old rules. */ - /* Info leak: The player knows if they know the target tile. */ - if (!plr_knows_tile(actor->player, target->tile)) { - return TRI_NO; - } - - if (plr_sees_tile(actor->player, target->tile)) { + } else if (paction->result == ACTRES_PARADROP + || paction->result == ACTRES_PARADROP_CONQUER) { + if (can_player_see_tile(actor->player, target->tile)) { /* Check for seen stuff that may kill the actor unit. */ /* Reason: Keep the old rules. Be merciful. */ @@ -4084,647 +3847,6 @@ is_action_possible(const action_id wanted_action, } } unit_list_iterate_end; } - - /* Reason: Keep paratroopers_range working. */ - /* Info leak: The player knows the location of the actor and of the - * target tile. */ - if (unit_type_get(actor->unit)->paratroopers_range - < real_map_distance(actor->tile, target->tile)) { - return TRI_NO; - } - - break; - - case ACTRES_AIRLIFT: - /* Reason: Keep the old rules. */ - /* Info leak: same as test_unit_can_airlift_to() */ - switch (test_unit_can_airlift_to(omniscient ? NULL : actor->player, - actor->unit, target->city)) { - case AR_OK: - return TRI_YES; - case AR_OK_SRC_UNKNOWN: - case AR_OK_DST_UNKNOWN: - return TRI_MAYBE; - case AR_NO_MOVES: - case AR_WRONG_UNITTYPE: - case AR_OCCUPIED: - case AR_NOT_IN_CITY: - case AR_BAD_SRC_CITY: - case AR_BAD_DST_CITY: - case AR_SRC_NO_FLIGHTS: - case AR_DST_NO_FLIGHTS: - return TRI_NO; - } - - break; - - case ACTRES_ATTACK: - /* Reason: Keep the old rules. */ - if (!can_unit_attack_tile(actor->unit, paction, target->tile)) { - return TRI_NO; - } - break; - - case ACTRES_WIPE_UNITS: - if (!can_unit_attack_tile(actor->unit, paction, target->tile)) { - return TRI_NO; - } - - unit_list_iterate(target->tile->units, punit) { - if (get_total_defense_power(actor->unit, punit) > 0) { - return TRI_NO; - } - } unit_list_iterate_end; - break; - - case ACTRES_CONQUER_CITY: - /* Reason: "Conquer City" involves moving into the city. */ - if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, - FALSE, FALSE, TRUE)) { - return TRI_NO; - } - - break; - - case ACTRES_CONQUER_EXTRAS: - /* Reason: "Conquer Extras" involves moving to the tile. */ - if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, - FALSE, FALSE, FALSE)) { - return TRI_NO; - } - /* Reason: Must have something to claim. The more specific restriction - * that the base must be native to the actor unit is hard coded in - * unit_move(), in action_actor_utype_hard_reqs_ok_full(), in - * helptext_extra(), in helptext_unit(), in do_attack() and in - * diplomat_bribe(). */ - if (!tile_has_claimable_base(target->tile, actor->unittype)) { - return TRI_NO; - } - break; - - case ACTRES_HEAL_UNIT: - /* Reason: It is not the healthy who need a doctor, but the sick. */ - /* Info leak: the actor can see the target's HP. */ - if (!can_see_tgt_unit) { - return TRI_MAYBE; - } - if (!(target->unit->hp < target->unittype->hp)) { - return TRI_NO; - } - break; - - case ACTRES_TRANSFORM_TERRAIN: - pterrain = tile_terrain(target->tile); - if (pterrain->transform_result == T_NONE - || pterrain == pterrain->transform_result - || !terrain_surroundings_allow_change(target->tile, - pterrain->transform_result) - || (terrain_has_flag(pterrain->transform_result, TER_NO_CITIES) - && (tile_city(target->tile)))) { - return TRI_NO; - } - break; - - case ACTRES_CULTIVATE: - pterrain = tile_terrain(target->tile); - if (pterrain->cultivate_result == NULL) { - return TRI_NO; - } - if (!terrain_surroundings_allow_change(target->tile, - pterrain->cultivate_result) - || (terrain_has_flag(pterrain->cultivate_result, TER_NO_CITIES) - && tile_city(target->tile))) { - return TRI_NO; - } - break; - - case ACTRES_PLANT: - pterrain = tile_terrain(target->tile); - if (pterrain->plant_result == NULL) { - return TRI_NO; - } - if (!terrain_surroundings_allow_change(target->tile, - pterrain->plant_result) - || (terrain_has_flag(pterrain->plant_result, TER_NO_CITIES) - && tile_city(target->tile))) { - return TRI_NO; - } - break; - - case ACTRES_ROAD: - if (target_extra == NULL) { - return TRI_NO; - } - if (!is_extra_caused_by(target_extra, EC_ROAD)) { - /* Reason: This is not a road. */ - return TRI_NO; - } - if (!can_build_road(extra_road_get(target_extra), actor->unit, - target->tile)) { - return TRI_NO; - } - break; - - case ACTRES_BASE: - if (target_extra == NULL) { - return TRI_NO; - } - if (!is_extra_caused_by(target_extra, EC_BASE)) { - /* Reason: This is not a base. */ - return TRI_NO; - } - if (!can_build_base(actor->unit, - extra_base_get(target_extra), target->tile)) { - return TRI_NO; - } - break; - - case ACTRES_MINE: - if (target_extra == NULL) { - return TRI_NO; - } - if (!is_extra_caused_by(target_extra, EC_MINE)) { - /* Reason: This is not a mine. */ - return TRI_NO; - } - - if (!can_build_extra(target_extra, actor->unit, target->tile)) { - return TRI_NO; - } - break; - - case ACTRES_IRRIGATE: - if (target_extra == NULL) { - return TRI_NO; - } - if (!is_extra_caused_by(target_extra, EC_IRRIGATION)) { - /* Reason: This is not an irrigation. */ - return TRI_NO; - } - - if (!can_build_extra(target_extra, actor->unit, target->tile)) { - return TRI_NO; - } - break; - - case ACTRES_PILLAGE: - pterrain = tile_terrain(target->tile); - if (pterrain->pillage_time == 0) { - return TRI_NO; - } - - { - bv_extras pspresent = get_tile_infrastructure_set(target->tile, NULL); - bv_extras psworking = get_unit_tile_pillage_set(target->tile); - bv_extras pspossible; - - BV_CLR_ALL(pspossible); - extra_type_iterate(pextra) { - int idx = extra_index(pextra); - - /* Only one unit can pillage a given improvement at a time */ - if (BV_ISSET(pspresent, idx) - && (!BV_ISSET(psworking, idx) - || actor->unit->activity_target == pextra) - && can_remove_extra(pextra, actor->unit, target->tile)) { - bool required = FALSE; - - extra_type_iterate(pdepending) { - if (BV_ISSET(pspresent, extra_index(pdepending))) { - extra_deps_iterate(&(pdepending->reqs), pdep) { - if (pdep == pextra) { - required = TRUE; - break; - } - } extra_deps_iterate_end; - } - if (required) { - break; - } - } extra_type_iterate_end; - - if (!required) { - BV_SET(pspossible, idx); - } - } - } extra_type_iterate_end; - - if (!BV_ISSET_ANY(pspossible)) { - /* Nothing available to pillage */ - return TRI_NO; - } - - if (target_extra != NULL) { - if (!game.info.pillage_select) { - /* Hobson's choice (this case mostly exists for old clients) */ - /* Needs to match what unit_activity_assign_target chooses */ - struct extra_type *tgt; - - tgt = get_preferred_pillage(pspossible); - - if (tgt != target_extra) { - /* Only one target allowed, which wasn't the requested one */ - return TRI_NO; - } - } - - if (!BV_ISSET(pspossible, extra_index(target_extra))) { - return TRI_NO; - } - } - } - break; - - case ACTRES_CLEAN: - { - const struct extra_type *pextra = NULL; - const struct extra_type *fextra = NULL; - - pterrain = tile_terrain(target->tile); - - if (target_extra != NULL) { - if (is_extra_removed_by(target_extra, ERM_CLEANPOLLUTION) - && tile_has_extra(target->tile, target_extra)) { - pextra = target_extra; - } - if (is_extra_removed_by(target_extra, ERM_CLEANFALLOUT) - && tile_has_extra(target->tile, target_extra)) { - fextra = target_extra; - } - } else { - /* TODO: Make sure that all callers set target so that - * we don't need this fallback. */ - - pextra = prev_extra_in_tile(target->tile, - ERM_CLEANPOLLUTION, - actor->player, - actor->unit); - fextra = prev_extra_in_tile(target->tile, - ERM_CLEANFALLOUT, - actor->player, - actor->unit); - } - - if (pextra != NULL && pterrain->extra_removal_times[extra_index(pextra)] > 0 - && can_remove_extra(pextra, actor->unit, target->tile)) { - return TRI_YES; - } - - if (fextra != NULL && pterrain->extra_removal_times[extra_index(fextra)] > 0 - && can_remove_extra(fextra, actor->unit, target->tile)) { - return TRI_YES; - } - - return TRI_NO; - } - - case ACTRES_CLEAN_POLLUTION: - { - const struct extra_type *pextra; - - pterrain = tile_terrain(target->tile); - - if (target_extra != NULL) { - pextra = target_extra; - - if (!is_extra_removed_by(pextra, ERM_CLEANPOLLUTION)) { - return TRI_NO; - } - - if (!tile_has_extra(target->tile, pextra)) { - return TRI_NO; - } - } else { - /* TODO: Make sure that all callers set target so that - * we don't need this fallback. */ - pextra = prev_extra_in_tile(target->tile, - ERM_CLEANPOLLUTION, - actor->player, - actor->unit); - if (pextra == NULL) { - /* No available pollution extras */ - return TRI_NO; - } - } - - if (pterrain->extra_removal_times[extra_index(pextra)] == 0) { - return TRI_NO; - } - - if (can_remove_extra(pextra, actor->unit, target->tile)) { - return TRI_YES; - } - - return TRI_NO; - } - break; - - case ACTRES_CLEAN_FALLOUT: - { - const struct extra_type *pextra; - - pterrain = tile_terrain(target->tile); - - if (target_extra != NULL) { - pextra = target_extra; - - if (!is_extra_removed_by(pextra, ERM_CLEANFALLOUT)) { - return TRI_NO; - } - - if (!tile_has_extra(target->tile, pextra)) { - return TRI_NO; - } - } else { - /* TODO: Make sure that all callers set target so that - * we don't need this fallback. */ - pextra = prev_extra_in_tile(target->tile, - ERM_CLEANFALLOUT, - actor->player, - actor->unit); - if (pextra == NULL) { - /* No available fallout extras */ - return TRI_NO; - } - } - - if (pterrain->extra_removal_times[extra_index(pextra)] == 0) { - return TRI_NO; - } - - if (can_remove_extra(pextra, actor->unit, target->tile)) { - return TRI_YES; - } - - return TRI_NO; - } - break; - - case ACTRES_TRANSPORT_DEBOARD: - if (!can_unit_unload(actor->unit, target->unit)) { - /* Keep the old rules about Unreachable and disembarks. */ - return TRI_NO; - } - break; - - case ACTRES_TRANSPORT_BOARD: - if (unit_transported(actor->unit)) { - if (target->unit == unit_transport_get(actor->unit)) { - /* Already inside this transport. */ - return TRI_NO; - } - } - if (!could_unit_load(actor->unit, target->unit)) { - /* Keep the old rules. */ - return TRI_NO; - } - break; - - case ACTRES_TRANSPORT_LOAD: - if (unit_transported(target->unit)) { - if (actor->unit == unit_transport_get(target->unit)) { - /* Already transporting this unit. */ - return TRI_NO; - } - } - if (!could_unit_load(target->unit, actor->unit)) { - /* Keep the old rules. */ - return TRI_NO; - } - if (unit_transported(target->unit)) { - if (!can_unit_unload(target->unit, - unit_transport_get(target->unit))) { - /* Can't leave current transport. */ - return TRI_NO; - } - } - break; - - case ACTRES_TRANSPORT_UNLOAD: - if (!can_unit_unload(target->unit, actor->unit)) { - /* Keep the old rules about Unreachable and disembarks. */ - return TRI_NO; - } - break; - - case ACTRES_TRANSPORT_DISEMBARK: - if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, - FALSE, FALSE, FALSE)) { - /* Reason: involves moving to the tile. */ - return TRI_NO; - } - - /* We cannot move a transport into a tile that holds - * units or cities not allied with all of our cargo. */ - if (get_transporter_capacity(actor->unit) > 0) { - unit_list_iterate(unit_tile(actor->unit)->units, pcargo) { - if (unit_contained_in(pcargo, actor->unit) - && (is_non_allied_unit_tile(target->tile, unit_owner(pcargo)) - || is_non_allied_city_tile(target->tile, - unit_owner(pcargo)))) { - return TRI_NO; - } - } unit_list_iterate_end; - } - break; - - case ACTRES_TRANSPORT_EMBARK: - if (unit_transported(actor->unit)) { - if (target->unit == unit_transport_get(actor->unit)) { - /* Already inside this transport. */ - return TRI_NO; - } - } - if (!could_unit_load(actor->unit, target->unit)) { - /* Keep the old rules. */ - return TRI_NO; - } - if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, - FALSE, TRUE, FALSE)) { - /* Reason: involves moving to the tile. */ - return TRI_NO; - } - /* We cannot move a transport into a tile that holds - * units or cities not allied with all of our cargo. */ - if (get_transporter_capacity(actor->unit) > 0) { - unit_list_iterate(unit_tile(actor->unit)->units, pcargo) { - if (unit_contained_in(pcargo, actor->unit) - && (is_non_allied_unit_tile(target->tile, unit_owner(pcargo)) - || is_non_allied_city_tile(target->tile, - unit_owner(pcargo)))) { - return TRI_NO; - } - } unit_list_iterate_end; - } - break; - - case ACTRES_SPY_ATTACK: - { - bool found; - - if (!omniscient - && !can_player_see_hypotetic_units_at(actor->player, - target->tile)) { - /* May have a hidden diplomatic defender. */ - return TRI_MAYBE; - } - - found = FALSE; - unit_list_iterate(target->tile->units, punit) { - struct player *uplayer = unit_owner(punit); - - if (uplayer == actor->player) { - /* Won't defend against its owner. */ - continue; - } - - if (unit_has_type_flag(punit, UTYF_SUPERSPY)) { - /* This unbeatable diplomatic defender will defend before any - * that can be beaten. */ - found = FALSE; - break; - } - - if (unit_has_type_flag(punit, UTYF_DIPLOMAT)) { - /* Found a beatable diplomatic defender. */ - found = TRUE; - break; - } - } unit_list_iterate_end; - - if (!found) { - return TRI_NO; - } - } - break; - - case ACTRES_HUT_ENTER: - case ACTRES_HUT_FRIGHTEN: - /* Reason: involves moving to the tile. */ - if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, - FALSE, FALSE, FALSE)) { - return TRI_NO; - } - if (!unit_can_displace_hut(actor->unit, target->tile)) { - /* Reason: keep extra rmreqs working. */ - return TRI_NO; - } - break; - - case ACTRES_UNIT_MOVE: - case ACTRES_TELEPORT: - - if (paction->result == ACTRES_UNIT_MOVE) { - /* Reason: is moving to the tile. */ - if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, - FALSE, FALSE, FALSE)) { - return TRI_NO; - } - } else { - fc_assert(paction->result == ACTRES_TELEPORT); - - /* Reason: is teleporting to the tile. */ - if (!unit_can_teleport_to_tile(&(wld.map), actor->unit, target->tile, - FALSE, FALSE)) { - return TRI_NO; - } - } - - /* Reason: Don't override "Transport Embark" */ - if (!can_unit_exist_at_tile(&(wld.map), actor->unit, target->tile)) { - return TRI_NO; - } - - /* We cannot move a transport into a tile that holds - * units or cities not allied with all of our cargo. */ - if (get_transporter_capacity(actor->unit) > 0) { - unit_list_iterate(unit_tile(actor->unit)->units, pcargo) { - if (unit_contained_in(pcargo, actor->unit) - && (is_non_allied_unit_tile(target->tile, unit_owner(pcargo)) - || is_non_allied_city_tile(target->tile, - unit_owner(pcargo)))) { - return TRI_NO; - } - } unit_list_iterate_end; - } - break; - - case ACTRES_SPY_ESCAPE: - /* Reason: Be merciful. */ - /* Info leak: The player know if they have any cities. */ - if (city_list_size(actor->player->cities) < 1) { - return TRI_NO; - } - break; - - case ACTRES_SPY_SPREAD_PLAGUE: - /* Enabling this action with illness_on = FALSE prevents spread. */ - break; - case ACTRES_BOMBARD: - { - /* Allow bombing only if there's reachable units in the target - * tile. Bombing without anybody taking damage is certainly not - * what user really wants. - * Allow bombing when player does not know if there's units in - * target tile. */ - if (tile_city(target->tile) == NULL - && fc_funcs->player_tile_vision_get(target->tile, actor->player, - V_MAIN) - && fc_funcs->player_tile_vision_get(target->tile, actor->player, - V_INVIS) - && fc_funcs->player_tile_vision_get(target->tile, actor->player, - V_SUBSURFACE)) { - bool hiding_extra = FALSE; - - extra_type_iterate(pextra) { - if (pextra->eus == EUS_HIDDEN - && tile_has_extra(target->tile, pextra)) { - hiding_extra = TRUE; - break; - } - } extra_type_iterate_end; - - if (!hiding_extra) { - bool has_target = FALSE; - - unit_list_iterate(target->tile->units, punit) { - if (is_unit_reachable_at(punit, actor->unit, target->tile)) { - has_target = TRUE; - break; - } - } unit_list_iterate_end; - - if (!has_target) { - return TRI_NO; - } - } - } - } - break; - case ACTRES_ESTABLISH_EMBASSY: - case ACTRES_SPY_INVESTIGATE_CITY: - case ACTRES_SPY_POISON: - case ACTRES_SPY_SABOTAGE_CITY: - case ACTRES_SPY_TARGETED_SABOTAGE_CITY: - case ACTRES_SPY_SABOTAGE_CITY_PRODUCTION: - case ACTRES_SPY_STEAL_TECH: - case ACTRES_SPY_INCITE_CITY: - case ACTRES_SPY_SABOTAGE_UNIT: - case ACTRES_STEAL_MAPS: - case ACTRES_SPY_NUKE: - case ACTRES_NUKE: - case ACTRES_DESTROY_CITY: - case ACTRES_EXPEL_UNIT: - case ACTRES_DISBAND_UNIT: - case ACTRES_CONVERT: - case ACTRES_STRIKE_BUILDING: - case ACTRES_STRIKE_PRODUCTION: - case ACTRES_FORTIFY: - case ACTRES_HOMELESS: - case ACTRES_ENABLER_CHECK: - case ACTRES_NONE: - /* No known hard coded requirements. */ - break; } return out; diff --git a/common/actres.c b/common/actres.c index ec383b6037..b9013e688f 100644 --- a/common/actres.c +++ b/common/actres.c @@ -15,6 +15,19 @@ #include #endif +/* common */ +#include "city.h" +#include "combat.h" +#include "fc_interface.h" +#include "game.h" +#include "map.h" +#include "metaknowledge.h" +#include "movement.h" +#include "player.h" +#include "requirements.h" +#include "tile.h" +#include "traderoutes.h" + #include "actres.h" static struct actres act_results[ACTRES_LAST] = { @@ -208,3 +221,902 @@ bool actres_is_hostile(enum action_result result) return act_results[result].hostile; } + +/**********************************************************************//** + Returns TRUE iff the specified player knows (has seen) the specified + tile. +**************************************************************************/ +static bool plr_knows_tile(const struct player *plr, + const struct tile *ttile) +{ + return plr != nullptr + && ttile != nullptr + && (tile_get_known(ttile, plr) != TILE_UNKNOWN); +} + +/**********************************************************************//** + Could an action with this kind of result be made? +**************************************************************************/ +enum fc_tristate actres_possible(enum action_result result, + const struct req_context *actor, + const struct req_context *target, + const struct extra_type *target_extra, + enum fc_tristate def, + bool omniscient, + const struct city *homecity) +{ + struct terrain *pterrain; + bool can_see_tgt_unit; + bool can_see_tgt_tile; + + /* Only check requirement against the target unit if the actor can see it + * or if the evaluator is omniscient. The game checking the rules is + * omniscient. The player asking about their odds isn't. */ + can_see_tgt_unit = (omniscient || (target->unit + && can_player_see_unit(actor->player, + target->unit))); + + /* Only check requirement against the target tile if the actor can see it + * or if the evaluator is omniscient. The game checking the rules is + * omniscient. The player asking about their odds isn't. */ + can_see_tgt_tile = (omniscient + || can_player_see_tile(actor->player, target->tile)); + + switch (result) { + case ACTRES_CAPTURE_UNITS: + case ACTRES_SPY_BRIBE_UNIT: + /* Why this is a hard requirement: Can't transfer a unique unit if the + * actor player already has one. */ + /* Info leak: This is only checked for when the actor player can see + * the target unit. Since the target unit is seen its type is known. + * The fact that a city hiding the unseen unit is occupied is known. */ + + if (!can_see_tgt_unit) { + /* An omniscient player can see the target unit. */ + fc_assert(!omniscient); + + return TRI_MAYBE; + } + + if (utype_player_already_has_this_unique(actor->player, + target->unittype)) { + return TRI_NO; + } + + /* 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(). */ + + break; + + case ACTRES_SPY_TARGETED_STEAL_TECH: + /* Reason: The Freeciv code don't support selecting a target tech + * unless it is known that the victim player has it. */ + /* Info leak: The actor player knowns whose techs they can see. */ + if (!can_see_techs_of_target(actor->player, target->player)) { + return TRI_NO; + } + + break; + + case ACTRES_SPY_STEAL_GOLD: + /* If actor->unit can do the action the actor->player can see how much + * gold target->player have. Not requiring it is therefore pointless. + */ + if (target->player->economic.gold <= 0) { + return TRI_NO; + } + + break; + + case ACTRES_TRADE_ROUTE: + case ACTRES_MARKETPLACE: + { + /* Checked in action_hard_reqs_actor() */ + fc_assert_ret_val(homecity != NULL, TRI_NO); + + /* Can't establish a trade route or enter the market place if the + * cities can't trade at all. */ + /* TODO: Should this restriction (and the above restriction that the + * actor unit must have a home city) be kept for Enter Marketplace? */ + if (!can_cities_trade(homecity, target->city)) { + return TRI_NO; + } + + /* There are more restrictions on establishing a trade route than on + * entering the market place. */ + if (result == ACTRES_TRADE_ROUTE + && !can_establish_trade_route(homecity, target->city)) { + return TRI_NO; + } + } + + break; + + case ACTRES_HELP_WONDER: + case ACTRES_DISBAND_UNIT_RECOVER: + /* It is only possible to help the production if the production needs + * the help. (If not it would be possible to add shields for something + * that can't legally receive help if it is build later) */ + /* Info leak: The player knows that the production in their own city has + * been hurried (bought or helped). The information isn't revealed when + * asking for action probabilities since omniscient is FALSE. */ + if (!omniscient + && !can_player_see_city_internals(actor->player, target->city)) { + return TRI_MAYBE; + } + + if (!(target->city->shield_stock + < city_production_build_shield_cost(target->city))) { + return TRI_NO; + } + + break; + + case ACTRES_FOUND_CITY: + if (game.scenario.prevent_new_cities) { + /* Reason: allow scenarios to disable city founding. */ + /* Info leak: the setting is public knowledge. */ + return TRI_NO; + } + + if (can_see_tgt_tile && tile_city(target->tile)) { + /* Reason: a tile can have 0 or 1 cities. */ + return TRI_NO; + } + + if (citymindist_prevents_city_on_tile(target->tile)) { + if (omniscient) { + /* No need to check again. */ + return TRI_NO; + } else { + square_iterate(&(wld.map), target->tile, + game.info.citymindist - 1, otile) { + if (tile_city(otile) != NULL + && can_player_see_tile(actor->player, otile)) { + /* Known to be blocked by citymindist */ + return TRI_NO; + } + } square_iterate_end; + } + } + + /* The player may not have enough information to be certain. */ + + if (!can_see_tgt_tile) { + /* Need to know if target tile already has a city, has TER_NO_CITIES + * terrain, is non native to the actor or is owned by a foreigner. */ + return TRI_MAYBE; + } + + if (!omniscient) { + /* The player may not have enough information to find out if + * citymindist blocks or not. This doesn't depend on if it blocks. */ + square_iterate(&(wld.map), target->tile, + game.info.citymindist - 1, otile) { + if (!can_player_see_tile(actor->player, otile)) { + /* Could have a city that blocks via citymindist. Even if this + * tile has TER_NO_CITIES terrain the player don't know that it + * didn't change and had a city built on it. */ + return TRI_MAYBE; + } + } square_iterate_end; + } + + break; + + case ACTRES_JOIN_CITY: + { + int new_pop; + + if (!omniscient + && !player_can_see_city_externals(actor->player, target->city)) { + return TRI_MAYBE; + } + + new_pop = city_size_get(target->city) + unit_pop_value(actor->unit); + + if (new_pop > game.info.add_to_size_limit) { + /* Reason: Make the add_to_size_limit setting work. */ + return TRI_NO; + } + + if (!city_can_grow_to(target->city, new_pop)) { + /* Reason: respect city size limits. */ + /* Info leak: when it is legal to join a foreign city is legal and + * the EFT_SIZE_UNLIMIT effect or the EFT_SIZE_ADJ effect depends on + * something the actor player don't have access to. + * Example: depends on a building (like Aqueduct) that isn't + * VisibleByOthers. */ + return TRI_NO; + } + } + + break; + + case ACTRES_NUKE_UNITS: + /* Has only action specific requirements */ + break; + + case ACTRES_HOME_CITY: + /* Reason: can't change to what is. */ + /* Info leak: The player knows their unit's current home city. */ + if (homecity != NULL && homecity->id == target->city->id) { + /* This is already the unit's home city. */ + return TRI_NO; + } + + { + int slots = unit_type_get(actor->unit)->city_slots; + + if (slots > 0 && city_unit_slots_available(target->city) < slots) { + return TRI_NO; + } + } + + break; + + case ACTRES_UPGRADE_UNIT: + /* Reason: Keep the old rules. */ + /* Info leak: The player knows their unit's type. They know if they can + * build the unit type upgraded to. If the upgrade happens in a foreign + * city that fact may leak. This can be seen as a price for changing + * the rules to allow upgrading in a foreign city. + * The player knows how much gold they have. If the Upgrade_Price_Pct + * effect depends on information they don't have that information may + * leak. The player knows the location of their unit. They know if the + * tile has a city and if the unit can exist there outside a transport. + * The player knows their unit's cargo. By knowing the number and type + * of cargo, they can predict if there will be enough room in the unit + * upgraded to, as long as they know what unit type their unit will end + * up as. */ + if (unit_upgrade_test(actor->unit, FALSE) != UU_OK) { + return TRI_NO; + } + + break; + + case ACTRES_PARADROP: + case ACTRES_PARADROP_CONQUER: + /* Reason: Keep the old rules. */ + /* Info leak: The player knows if they know the target tile. */ + if (!plr_knows_tile(actor->player, target->tile)) { + return TRI_NO; + } + + /* Reason: Keep paratroopers_range working. */ + /* Info leak: The player knows the location of the actor and of the + * target tile. */ + if (unit_type_get(actor->unit)->paratroopers_range + < real_map_distance(actor->tile, target->tile)) { + return TRI_NO; + } + + break; + + case ACTRES_AIRLIFT: + /* Reason: Keep the old rules. */ + /* Info leak: same as test_unit_can_airlift_to() */ + switch (test_unit_can_airlift_to(omniscient ? NULL : actor->player, + actor->unit, target->city)) { + case AR_OK: + return TRI_YES; + case AR_OK_SRC_UNKNOWN: + case AR_OK_DST_UNKNOWN: + return TRI_MAYBE; + case AR_NO_MOVES: + case AR_WRONG_UNITTYPE: + case AR_OCCUPIED: + case AR_NOT_IN_CITY: + case AR_BAD_SRC_CITY: + case AR_BAD_DST_CITY: + case AR_SRC_NO_FLIGHTS: + case AR_DST_NO_FLIGHTS: + return TRI_NO; + } + + break; + + case ACTRES_ATTACK: + break; + + case ACTRES_WIPE_UNITS: + unit_list_iterate(target->tile->units, punit) { + if (get_total_defense_power(actor->unit, punit) > 0) { + return TRI_NO; + } + } unit_list_iterate_end; + break; + + case ACTRES_CONQUER_CITY: + /* Reason: "Conquer City" involves moving into the city. */ + if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, + FALSE, FALSE, TRUE)) { + return TRI_NO; + } + + break; + + case ACTRES_CONQUER_EXTRAS: + /* Reason: "Conquer Extras" involves moving to the tile. */ + if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, + FALSE, FALSE, FALSE)) { + return TRI_NO; + } + /* Reason: Must have something to claim. The more specific restriction + * that the base must be native to the actor unit is hard coded in + * unit_move(), in action_actor_utype_hard_reqs_ok_full(), in + * helptext_extra(), in helptext_unit(), in do_attack() and in + * diplomat_bribe(). */ + if (!tile_has_claimable_base(target->tile, actor->unittype)) { + return TRI_NO; + } + break; + + case ACTRES_HEAL_UNIT: + /* Reason: It is not the healthy who need a doctor, but the sick. */ + /* Info leak: the actor can see the target's HP. */ + if (!can_see_tgt_unit) { + return TRI_MAYBE; + } + if (!(target->unit->hp < target->unittype->hp)) { + return TRI_NO; + } + break; + + case ACTRES_TRANSFORM_TERRAIN: + pterrain = tile_terrain(target->tile); + if (pterrain->transform_result == T_NONE + || pterrain == pterrain->transform_result + || !terrain_surroundings_allow_change(target->tile, + pterrain->transform_result) + || (terrain_has_flag(pterrain->transform_result, TER_NO_CITIES) + && (tile_city(target->tile)))) { + return TRI_NO; + } + break; + + case ACTRES_CULTIVATE: + pterrain = tile_terrain(target->tile); + if (pterrain->cultivate_result == NULL) { + return TRI_NO; + } + if (!terrain_surroundings_allow_change(target->tile, + pterrain->cultivate_result) + || (terrain_has_flag(pterrain->cultivate_result, TER_NO_CITIES) + && tile_city(target->tile))) { + return TRI_NO; + } + break; + + case ACTRES_PLANT: + pterrain = tile_terrain(target->tile); + if (pterrain->plant_result == NULL) { + return TRI_NO; + } + if (!terrain_surroundings_allow_change(target->tile, + pterrain->plant_result) + || (terrain_has_flag(pterrain->plant_result, TER_NO_CITIES) + && tile_city(target->tile))) { + return TRI_NO; + } + break; + + case ACTRES_ROAD: + if (target_extra == NULL) { + return TRI_NO; + } + if (!is_extra_caused_by(target_extra, EC_ROAD)) { + /* Reason: This is not a road. */ + return TRI_NO; + } + if (!can_build_road(extra_road_get(target_extra), actor->unit, + target->tile)) { + return TRI_NO; + } + break; + + case ACTRES_BASE: + if (target_extra == NULL) { + return TRI_NO; + } + if (!is_extra_caused_by(target_extra, EC_BASE)) { + /* Reason: This is not a base. */ + return TRI_NO; + } + if (!can_build_base(actor->unit, + extra_base_get(target_extra), target->tile)) { + return TRI_NO; + } + break; + + case ACTRES_MINE: + if (target_extra == NULL) { + return TRI_NO; + } + if (!is_extra_caused_by(target_extra, EC_MINE)) { + /* Reason: This is not a mine. */ + return TRI_NO; + } + + if (!can_build_extra(target_extra, actor->unit, target->tile)) { + return TRI_NO; + } + break; + + case ACTRES_IRRIGATE: + if (target_extra == NULL) { + return TRI_NO; + } + if (!is_extra_caused_by(target_extra, EC_IRRIGATION)) { + /* Reason: This is not an irrigation. */ + return TRI_NO; + } + + if (!can_build_extra(target_extra, actor->unit, target->tile)) { + return TRI_NO; + } + break; + + case ACTRES_PILLAGE: + pterrain = tile_terrain(target->tile); + if (pterrain->pillage_time == 0) { + return TRI_NO; + } + + { + bv_extras pspresent = get_tile_infrastructure_set(target->tile, NULL); + bv_extras psworking = get_unit_tile_pillage_set(target->tile); + bv_extras pspossible; + + BV_CLR_ALL(pspossible); + extra_type_iterate(pextra) { + int idx = extra_index(pextra); + + /* Only one unit can pillage a given improvement at a time */ + if (BV_ISSET(pspresent, idx) + && (!BV_ISSET(psworking, idx) + || actor->unit->activity_target == pextra) + && can_remove_extra(pextra, actor->unit, target->tile)) { + bool required = FALSE; + + extra_type_iterate(pdepending) { + if (BV_ISSET(pspresent, extra_index(pdepending))) { + extra_deps_iterate(&(pdepending->reqs), pdep) { + if (pdep == pextra) { + required = TRUE; + break; + } + } extra_deps_iterate_end; + } + if (required) { + break; + } + } extra_type_iterate_end; + + if (!required) { + BV_SET(pspossible, idx); + } + } + } extra_type_iterate_end; + + if (!BV_ISSET_ANY(pspossible)) { + /* Nothing available to pillage */ + return TRI_NO; + } + + if (target_extra != NULL) { + if (!game.info.pillage_select) { + /* Hobson's choice (this case mostly exists for old clients) */ + /* Needs to match what unit_activity_assign_target chooses */ + struct extra_type *tgt; + + tgt = get_preferred_pillage(pspossible); + + if (tgt != target_extra) { + /* Only one target allowed, which wasn't the requested one */ + return TRI_NO; + } + } + + if (!BV_ISSET(pspossible, extra_index(target_extra))) { + return TRI_NO; + } + } + } + break; + + case ACTRES_CLEAN: + { + const struct extra_type *pextra = NULL; + const struct extra_type *fextra = NULL; + + pterrain = tile_terrain(target->tile); + + if (target_extra != NULL) { + if (is_extra_removed_by(target_extra, ERM_CLEANPOLLUTION) + && tile_has_extra(target->tile, target_extra)) { + pextra = target_extra; + } + if (is_extra_removed_by(target_extra, ERM_CLEANFALLOUT) + && tile_has_extra(target->tile, target_extra)) { + fextra = target_extra; + } + } else { + /* TODO: Make sure that all callers set target so that + * we don't need this fallback. */ + + pextra = prev_extra_in_tile(target->tile, + ERM_CLEANPOLLUTION, + actor->player, + actor->unit); + fextra = prev_extra_in_tile(target->tile, + ERM_CLEANFALLOUT, + actor->player, + actor->unit); + } + + if (pextra != NULL && pterrain->extra_removal_times[extra_index(pextra)] > 0 + && can_remove_extra(pextra, actor->unit, target->tile)) { + return TRI_YES; + } + + if (fextra != NULL && pterrain->extra_removal_times[extra_index(fextra)] > 0 + && can_remove_extra(fextra, actor->unit, target->tile)) { + return TRI_YES; + } + + return TRI_NO; + } + + case ACTRES_CLEAN_POLLUTION: + { + const struct extra_type *pextra; + + pterrain = tile_terrain(target->tile); + + if (target_extra != NULL) { + pextra = target_extra; + + if (!is_extra_removed_by(pextra, ERM_CLEANPOLLUTION)) { + return TRI_NO; + } + + if (!tile_has_extra(target->tile, pextra)) { + return TRI_NO; + } + } else { + /* TODO: Make sure that all callers set target so that + * we don't need this fallback. */ + pextra = prev_extra_in_tile(target->tile, + ERM_CLEANPOLLUTION, + actor->player, + actor->unit); + if (pextra == NULL) { + /* No available pollution extras */ + return TRI_NO; + } + } + + if (pterrain->extra_removal_times[extra_index(pextra)] == 0) { + return TRI_NO; + } + + if (can_remove_extra(pextra, actor->unit, target->tile)) { + return TRI_YES; + } + + return TRI_NO; + } + break; + + case ACTRES_CLEAN_FALLOUT: + { + const struct extra_type *pextra; + + pterrain = tile_terrain(target->tile); + + if (target_extra != NULL) { + pextra = target_extra; + + if (!is_extra_removed_by(pextra, ERM_CLEANFALLOUT)) { + return TRI_NO; + } + + if (!tile_has_extra(target->tile, pextra)) { + return TRI_NO; + } + } else { + /* TODO: Make sure that all callers set target so that + * we don't need this fallback. */ + pextra = prev_extra_in_tile(target->tile, + ERM_CLEANFALLOUT, + actor->player, + actor->unit); + if (pextra == NULL) { + /* No available fallout extras */ + return TRI_NO; + } + } + + if (pterrain->extra_removal_times[extra_index(pextra)] == 0) { + return TRI_NO; + } + + if (can_remove_extra(pextra, actor->unit, target->tile)) { + return TRI_YES; + } + + return TRI_NO; + } + break; + + case ACTRES_TRANSPORT_DEBOARD: + if (!can_unit_unload(actor->unit, target->unit)) { + /* Keep the old rules about Unreachable and disembarks. */ + return TRI_NO; + } + break; + + case ACTRES_TRANSPORT_BOARD: + if (unit_transported(actor->unit)) { + if (target->unit == unit_transport_get(actor->unit)) { + /* Already inside this transport. */ + return TRI_NO; + } + } + if (!could_unit_load(actor->unit, target->unit)) { + /* Keep the old rules. */ + return TRI_NO; + } + break; + + case ACTRES_TRANSPORT_LOAD: + if (unit_transported(target->unit)) { + if (actor->unit == unit_transport_get(target->unit)) { + /* Already transporting this unit. */ + return TRI_NO; + } + } + if (!could_unit_load(target->unit, actor->unit)) { + /* Keep the old rules. */ + return TRI_NO; + } + if (unit_transported(target->unit)) { + if (!can_unit_unload(target->unit, + unit_transport_get(target->unit))) { + /* Can't leave current transport. */ + return TRI_NO; + } + } + break; + + case ACTRES_TRANSPORT_UNLOAD: + if (!can_unit_unload(target->unit, actor->unit)) { + /* Keep the old rules about Unreachable and disembarks. */ + return TRI_NO; + } + break; + + case ACTRES_TRANSPORT_DISEMBARK: + if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, + FALSE, FALSE, FALSE)) { + /* Reason: involves moving to the tile. */ + return TRI_NO; + } + + /* We cannot move a transport into a tile that holds + * units or cities not allied with all of our cargo. */ + if (get_transporter_capacity(actor->unit) > 0) { + unit_list_iterate(unit_tile(actor->unit)->units, pcargo) { + if (unit_contained_in(pcargo, actor->unit) + && (is_non_allied_unit_tile(target->tile, unit_owner(pcargo)) + || is_non_allied_city_tile(target->tile, + unit_owner(pcargo)))) { + return TRI_NO; + } + } unit_list_iterate_end; + } + break; + + case ACTRES_TRANSPORT_EMBARK: + if (unit_transported(actor->unit)) { + if (target->unit == unit_transport_get(actor->unit)) { + /* Already inside this transport. */ + return TRI_NO; + } + } + if (!could_unit_load(actor->unit, target->unit)) { + /* Keep the old rules. */ + return TRI_NO; + } + if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, + FALSE, TRUE, FALSE)) { + /* Reason: involves moving to the tile. */ + return TRI_NO; + } + /* We cannot move a transport into a tile that holds + * units or cities not allied with all of our cargo. */ + if (get_transporter_capacity(actor->unit) > 0) { + unit_list_iterate(unit_tile(actor->unit)->units, pcargo) { + if (unit_contained_in(pcargo, actor->unit) + && (is_non_allied_unit_tile(target->tile, unit_owner(pcargo)) + || is_non_allied_city_tile(target->tile, + unit_owner(pcargo)))) { + return TRI_NO; + } + } unit_list_iterate_end; + } + break; + + case ACTRES_SPY_ATTACK: + { + bool found; + + if (!omniscient + && !can_player_see_hypotetic_units_at(actor->player, + target->tile)) { + /* May have a hidden diplomatic defender. */ + return TRI_MAYBE; + } + + found = FALSE; + unit_list_iterate(target->tile->units, punit) { + struct player *uplayer = unit_owner(punit); + + if (uplayer == actor->player) { + /* Won't defend against its owner. */ + continue; + } + + if (unit_has_type_flag(punit, UTYF_SUPERSPY)) { + /* This unbeatable diplomatic defender will defend before any + * that can be beaten. */ + found = FALSE; + break; + } + + if (unit_has_type_flag(punit, UTYF_DIPLOMAT)) { + /* Found a beatable diplomatic defender. */ + found = TRUE; + break; + } + } unit_list_iterate_end; + + if (!found) { + return TRI_NO; + } + } + break; + + case ACTRES_HUT_ENTER: + case ACTRES_HUT_FRIGHTEN: + /* Reason: involves moving to the tile. */ + if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, + FALSE, FALSE, FALSE)) { + return TRI_NO; + } + if (!unit_can_displace_hut(actor->unit, target->tile)) { + /* Reason: keep extra rmreqs working. */ + return TRI_NO; + } + break; + + case ACTRES_UNIT_MOVE: + case ACTRES_TELEPORT: + + if (result == ACTRES_UNIT_MOVE) { + /* Reason: is moving to the tile. */ + if (!unit_can_move_to_tile(&(wld.map), actor->unit, target->tile, + FALSE, FALSE, FALSE)) { + return TRI_NO; + } + } else { + fc_assert(result == ACTRES_TELEPORT); + + /* Reason: is teleporting to the tile. */ + if (!unit_can_teleport_to_tile(&(wld.map), actor->unit, target->tile, + FALSE, FALSE)) { + return TRI_NO; + } + } + + /* Reason: Don't override "Transport Embark" */ + if (!can_unit_exist_at_tile(&(wld.map), actor->unit, target->tile)) { + return TRI_NO; + } + + /* We cannot move a transport into a tile that holds + * units or cities not allied with all of our cargo. */ + if (get_transporter_capacity(actor->unit) > 0) { + unit_list_iterate(unit_tile(actor->unit)->units, pcargo) { + if (unit_contained_in(pcargo, actor->unit) + && (is_non_allied_unit_tile(target->tile, unit_owner(pcargo)) + || is_non_allied_city_tile(target->tile, + unit_owner(pcargo)))) { + return TRI_NO; + } + } unit_list_iterate_end; + } + break; + + case ACTRES_SPY_ESCAPE: + /* Reason: Be merciful. */ + /* Info leak: The player know if they have any cities. */ + if (city_list_size(actor->player->cities) < 1) { + return TRI_NO; + } + break; + + case ACTRES_SPY_SPREAD_PLAGUE: + /* Enabling this action with illness_on = FALSE prevents spread. */ + break; + case ACTRES_BOMBARD: + { + /* Allow bombing only if there's reachable units in the target + * tile. Bombing without anybody taking damage is certainly not + * what user really wants. + * Allow bombing when player does not know if there's units in + * target tile. */ + if (tile_city(target->tile) == NULL + && fc_funcs->player_tile_vision_get(target->tile, actor->player, + V_MAIN) + && fc_funcs->player_tile_vision_get(target->tile, actor->player, + V_INVIS) + && fc_funcs->player_tile_vision_get(target->tile, actor->player, + V_SUBSURFACE)) { + bool hiding_extra = FALSE; + + extra_type_iterate(pextra) { + if (pextra->eus == EUS_HIDDEN + && tile_has_extra(target->tile, pextra)) { + hiding_extra = TRUE; + break; + } + } extra_type_iterate_end; + + if (!hiding_extra) { + bool has_target = FALSE; + + unit_list_iterate(target->tile->units, punit) { + if (is_unit_reachable_at(punit, actor->unit, target->tile)) { + has_target = TRUE; + break; + } + } unit_list_iterate_end; + + if (!has_target) { + return TRI_NO; + } + } + } + } + break; + case ACTRES_ESTABLISH_EMBASSY: + case ACTRES_SPY_INVESTIGATE_CITY: + case ACTRES_SPY_POISON: + case ACTRES_SPY_SABOTAGE_CITY: + case ACTRES_SPY_TARGETED_SABOTAGE_CITY: + case ACTRES_SPY_SABOTAGE_CITY_PRODUCTION: + case ACTRES_SPY_STEAL_TECH: + case ACTRES_SPY_INCITE_CITY: + case ACTRES_SPY_SABOTAGE_UNIT: + case ACTRES_STEAL_MAPS: + case ACTRES_SPY_NUKE: + case ACTRES_NUKE: + case ACTRES_DESTROY_CITY: + case ACTRES_EXPEL_UNIT: + case ACTRES_DISBAND_UNIT: + case ACTRES_CONVERT: + case ACTRES_STRIKE_BUILDING: + case ACTRES_STRIKE_PRODUCTION: + case ACTRES_FORTIFY: + case ACTRES_HOMELESS: + case ACTRES_ENABLER_CHECK: + case ACTRES_NONE: + /* No known hard coded requirements. */ + break; + } + + return def; +} diff --git a/common/actres.h b/common/actres.h index d63fa132de..20ca81a124 100644 --- a/common/actres.h +++ b/common/actres.h @@ -20,6 +20,7 @@ extern "C" { /* common */ #include "fc_types.h" +struct req_context; /* When making changes to this, update also atk_helpnames at actions.c */ #define SPECENUM_NAME action_target_kind @@ -103,6 +104,14 @@ enum act_tgt_compl actres_target_compl_calc(enum action_result result); enum action_battle_kind actres_get_battle_kind(enum action_result result); bool actres_is_hostile(enum action_result result); +enum fc_tristate actres_possible(enum action_result result, + const struct req_context *actor, + const struct req_context *target, + const struct extra_type *target_extra, + enum fc_tristate def, + bool omniscient, + const struct city *homecity); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/common/player.c b/common/player.c index 1e398cc2a9..90da00acef 100644 --- a/common/player.c +++ b/common/player.c @@ -1061,6 +1061,17 @@ bool can_player_see_unit_at(const struct player *pplayer, unit_type_get(punit)->vlayer); } +/**********************************************************************//** + Returns TRUE iff the specified player can see the specified tile. +**************************************************************************/ +bool can_player_see_tile(const struct player *plr, + const struct tile *ptile) +{ + return plr != nullptr + && ptile != nullptr + && (tile_get_known(ptile, plr) == TILE_KNOWN_SEEN); +} + /*******************************************************************//** Checks if a unit can be seen by pplayer at its current location. diff --git a/common/player.h b/common/player.h index 57ecf3e855..82fd7539c8 100644 --- a/common/player.h +++ b/common/player.h @@ -436,6 +436,8 @@ bool can_player_see_unit_at(const struct player *pplayer, const struct unit *punit, const struct tile *ptile, bool is_transported); +bool can_player_see_tile(const struct player *plr, + const struct tile *ptile); bool can_player_see_units_in_city(const struct player *pplayer, const struct city *pcity); -- 2.39.2