diff --git a/ai/default/daicity.c b/ai/default/daicity.c index c5a26e8c5e..cbd8ae8508 100644 --- a/ai/default/daicity.c +++ b/ai/default/daicity.c @@ -2081,6 +2081,7 @@ static bool dai_cant_help_req(const struct req_context *context, case VUT_TERRFLAG: case VUT_TERRAINALTER: case VUT_CITYTILE: + case VUT_CONNDIRBY: return !is_req_active(context, NULL, req, RPT_POSSIBLE); default: return is_req_preventing(context, NULL, req, RPT_POSSIBLE) > REQUCH_NO; diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index a307ba9a8a..085810d582 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -954,6 +954,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, case VUT_ACTION: case VUT_GOOD: case VUT_MINCALFRAG: + case VUT_CONNDIRBY: case VUT_COUNT: /* No sensible implementation possible with data available. */ break; diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c index 9fda555337..f42a811730 100644 --- a/ai/default/daimilitary.c +++ b/ai/default/daimilitary.c @@ -425,7 +425,7 @@ tactical_req_cb(const struct req_context *context, case VUT_UTFLAG: case VUT_UCLASS: case VUT_UCFLAG: - /* FIXME: support converting siege machines (needs hard reqs checked) */ + /* FIXME: Support converting siege machines (needs hard reqs checked) */ case VUT_ACTION: case VUT_OTYPE: case VUT_SPECIALIST: @@ -459,6 +459,7 @@ tactical_req_cb(const struct req_context *context, case VUT_TERRAINCLASS: case VUT_TERRFLAG: case VUT_TERRAINALTER: + case VUT_CONNDIRBY: case VUT_NONE: return tri_req_active(context, other_player, req); case VUT_COUNT: diff --git a/common/fc_types.h b/common/fc_types.h index e6776dfe41..a30ece4aa5 100644 --- a/common/fc_types.h +++ b/common/fc_types.h @@ -697,6 +697,7 @@ typedef union { struct specialist *specialist; struct terrain *terrain; struct unit_class *uclass; + struct unit_class *connector; const struct unit_type *utype; struct extra_type *extra; struct achievement *achievement; @@ -871,6 +872,8 @@ typedef union { #define SPECENUM_VALUE57NAME "FormAge" #define SPECENUM_VALUE58 VUT_MINCITIES #define SPECENUM_VALUE58NAME "MinCities" +#define SPECENUM_VALUE59 VUT_CONNDIRBY +#define SPECENUM_VALUE59NAME "ConnectedDirectlyBy" /* Keep this last. */ #define SPECENUM_COUNT VUT_COUNT diff --git a/common/map.c b/common/map.c index 55aac29595..187d24103a 100644 --- a/common/map.c +++ b/common/map.c @@ -1374,6 +1374,43 @@ int get_direction_for_step(const struct civ_map *nmap, return -1; } +/*******************************************************************//** + Return the tile in start_tile adjacency that is the closest + towards end_tile. On rectangular map, if the tiles are straightly + in cardinal direction, that direction is followed, otherwise + it goes diagonally. On hexagonal maps (or when the tiles are + double-opposite in a toroid world), a direction with smaller + number is preferred. If the tiles are the same, returns the input. + Note that it almost always gives different paths "to" and "from". + It is not the algorithm used in commercial Civilization II. + It's a slower but easier predictable one. + FIXME: if the line crosses a wrapped edge, give a way to follow + the longer "arc". +***********************************************************************/ +struct tile* step_towards(const struct civ_map *nmap, + struct tile *start_tile, + struct tile *end_tile) +{ + int dist = real_map_distance(start_tile, end_tile); + struct tile *res = start_tile; + + fc_assert_ret_val(NULL != start_tile && NULL != end_tile, NULL); + /* Some optimization might appear here? */ + adjc_iterate(nmap, start_tile, ptile) { + int ndist = real_map_distance(ptile, end_tile); + + if (ndist < dist + || (!ALL_DIRECTIONS_CARDINAL() && ndist == dist + && map_distance(ptile, end_tile) + < map_distance(res, end_tile))) { + dist = ndist; + res = ptile; + } + } adjc_iterate_end; + + return res; +} + /*******************************************************************//** Returns TRUE iff the move from the position (start_x, start_y) to (end_x, end_y) is a cardinal one. diff --git a/common/map.h b/common/map.h index 3acae9e6e1..562ecb74cc 100644 --- a/common/map.h +++ b/common/map.h @@ -69,7 +69,9 @@ bool base_get_direction_for_step(const struct civ_map *nmap, int get_direction_for_step(const struct civ_map *nmap, const struct tile *src_tile, const struct tile *dst_tile); - +struct tile* step_towards(const struct civ_map *nmap, + struct tile *start_tile, + struct tile *end_tile); /* Specific functions for start positions. */ struct startpos *map_startpos_by_number(int id); @@ -527,6 +529,23 @@ extern struct terrain_misc terrain_control; } \ } +/* Iterate over a "direct" path between tiles (see step_towards()) + * _src_tile appears only in _tile and _dst_tile only in _next_tile; + * if the tiles are the same, no iterations happen */ +#define direct_path_iterate(_map, _src_tile, _dst_tile, _tile, _next_tile) \ +{ \ + struct tile *_dst##_tile##__c = (_dst_tile); \ + struct tile *_tile##__buf, *_tile = (_src_tile); \ + \ + for (struct tile *_next_tile = step_towards(_map, _tile, _dst##_tile##__c);\ + !same_pos(_tile, _dst##_tile##__c); \ + (_tile##__buf = step_towards(_map, _next_tile, _dst##_tile##__c)), \ + (_tile = _next_tile), (_next_tile = _tile##__buf)) { + +#define direct_path_iterate_end \ + } \ +} + /* Iterate over all positions on the globe. * Use index positions for cache efficiency. */ #define whole_map_iterate(_map, _tile) \ diff --git a/common/metaknowledge.c b/common/metaknowledge.c index 3cfcff8c03..f702d12c94 100644 --- a/common/metaknowledge.c +++ b/common/metaknowledge.c @@ -19,10 +19,15 @@ #include "diptreaty.h" #include "game.h" #include "map.h" -#include "metaknowledge.h" +#include "movement.h" #include "tile.h" #include "traderoutes.h" +#include "metaknowledge.h" + +static bool can_plr_see_all_sym_diplrels_of(const struct player *pplayer, + const struct player *tplayer); + /**********************************************************************//** Returns TRUE iff the target_tile it self and all tiles cardinally adjacent to it are seen by pow_player. @@ -122,6 +127,89 @@ static bool is_tile_seen_trade_route(const struct player *pow_player, return TRUE; } +/**********************************************************************//** + Returns TRUE iff pplayer has enough data to tell if a direct path + exists from ptile to ttile for tplayer by connector. + The tiles must be known and seen, and should be safely clear + from non-allied military units. + Returns in sure case if the path exists into *pex. + All parametres exept tplayer must not be NULL +**************************************************************************/ +static bool +direct_path_known(const struct player *pplayer, bool *pex, + const struct player *tplayer, + const struct unit_class *connector, + struct tile *ptile, struct tile *ctile) +{ + bool sure = TRUE, ex = TRUE; + const struct civ_map *nmap = &(wld.map); + + if (ctile == ptile) { + sure = (TILE_KNOWN_SEEN == tile_get_known(ptile, pplayer)); + if (sure) { + ex = is_native_tile_to_class(connector, ctile); + } + } + direct_path_iterate(nmap, ptile, ctile, ftile, ttile) { + /* Actually, we can get intel about alliances seeing two units + * in one tile once. But currently we don't remember it for long. */ + bool dubious_unit + = !can_player_see_hypotetic_units_at(pplayer, ttile); + bool sure_unit = FALSE; + + if (TILE_KNOWN_SEEN != tile_get_known(ttile, pplayer)) { + sure = FALSE; + continue; + } + if (TILE_KNOWN_SEEN == tile_get_known(ftile, pplayer) + && !is_native_move(nmap, connector, ftile, ttile)) { + /* No way */ + *pex = FALSE; + return TRUE; + } + /* If we see a non-allied military unit at the tile, + * it is not passable. If we see it can't be here, it's passable. */ + if (tile_city(ttile) && city_owner(tile_city(ttile)) == tplayer) { + /* A non-allied unit may not hide here */ + continue; + } + unit_list_iterate(ttile->units, punit) { + if (can_player_see_unit(pplayer, punit)) { + if (is_attack_unit(punit) || is_guard_unit(punit)) { + if (tplayer) { + const struct player *o = unit_owner(punit); + + if (tplayer == o) { + /* Non-allied one can't be here */ + sure_unit = TRUE; + break; + } else if (can_plr_see_all_sym_diplrels_of(pplayer, tplayer) + || can_plr_see_all_sym_diplrels_of(pplayer, o)) { + if (!pplayers_allied(tplayer, o)) { + /* Way blocked */ + *pex = FALSE; + return TRUE; + } + } + } /* if tplayer */ + /* Might be a blocker */ + dubious_unit = TRUE; + } /* if !UTYF_CIVILAIN */ + } /* if can_player_see_unit */ + } unit_list_iterate_end; + if (dubious_unit && !sure_unit) { + sure = FALSE; + } + } direct_path_iterate_end; + + if (sure) { + /* We have checked all the path, and it is intact */ + *pex = ex; + } + + return sure; +} + /**********************************************************************//** Returns TRUE iff pplayer can see all the symmetric diplomatic relationships of tplayer. @@ -437,6 +525,81 @@ static bool is_req_knowable(const struct player *pov_player, } } + if (req->source.kind == VUT_CONNDIRBY) { + bool has_path = FALSE, no_way = TRUE; + + /* Must know and see the path */ + const struct unit_class *connector = req->source.value.connector; + const struct player *pplayer = context->player; + const struct city *pcity = context->city; + struct tile *ptile = (struct tile *)context->tile, *ctile; + + if (NULL == pcity || NULL == ptile || NULL == connector + || NULL == (ctile = city_tile(pcity))) { + /* Not enough data (sometimes may calculate but keep simple) */ + return prob_type == RPT_CERTAIN; + } + switch (req->range) { + case REQ_RANGE_TILE: + if (real_map_distance(ptile, ctile) > MAX_CONN_DIST) { + return TRUE; + } + return direct_path_known(pov_player, &has_path, + pplayer, connector, ptile, ctile); + case REQ_RANGE_CADJACENT: + circle_iterate(&(wld.map), ptile, 1, atile) { + const struct tile *stw = step_towards(&(wld.map), atile, ctile); + + if (map_distance(ptile, stw) > 1) { + if (real_map_distance(atile, ctile) > MAX_CONN_DIST) { + continue; + } + if (direct_path_known(pov_player, &has_path, + pplayer, connector, ptile, ctile)) { + if (has_path) { + return TRUE; + } + } else { + no_way = FALSE; + } + } + } circle_iterate_end; + + return no_way; + case REQ_RANGE_ADJACENT: + square_iterate(&(wld.map), ptile, 1, atile) { + const struct tile *stw = step_towards(&(wld.map), atile, ctile); + + if (real_map_distance(ptile, stw) > 1) { + if (real_map_distance(atile, ctile) > MAX_CONN_DIST) { + continue; + } + if (direct_path_known(pov_player, &has_path, + pplayer, connector, ptile, ctile)) { + if (has_path) { + return TRUE; + } + } else { + no_way = FALSE; + } + } + } square_iterate_end; + + return no_way; + case REQ_RANGE_CITY: + case REQ_RANGE_TRADE_ROUTE: + case REQ_RANGE_CONTINENT: + case REQ_RANGE_PLAYER: + case REQ_RANGE_TEAM: + case REQ_RANGE_ALLIANCE: + case REQ_RANGE_WORLD: + case REQ_RANGE_LOCAL: + case REQ_RANGE_COUNT: + /* Invalid range */ + return FALSE; + } + } + if (req->source.kind == VUT_IMPR_GENUS) { /* The only legal range when this was written was local. */ fc_assert(req->range == REQ_RANGE_LOCAL); diff --git a/common/movement.c b/common/movement.c index 04bfa7e8e3..a8f7b09747 100644 --- a/common/movement.c +++ b/common/movement.c @@ -458,6 +458,64 @@ bool is_native_move(const struct civ_map *nmap, return FALSE; } +/**********************************************************************//** + Function telling if two tiles are directly connected by uclass + for pplayer. "Directly" means "accessible by native moves in the most + natural path with no non-allied military units in the way". + "The most natural path" is paved by step_towards() function and is: + * asymmetric - different from ptile to ctile and from ctile to ptile; + * transitive - any tile on the path has TMNP to ctile along the path; + * not including start tile if it is not native for connector, + and never checks units at it. + connector, ptile and ctile must not be NULL. + + NOTE: to connect land cities by ocean-travelling class, one now + must specify a native extra present in coastal city tiles. +**************************************************************************/ +enum fc_tristate +uclass_directly_connects(const struct unit_class *connector, + const struct player *pplayer, + const struct tile *ptile, + const struct tile *ctile) +{ + bool definite = TRUE; + const struct civ_map *nmap = &(wld.map); + + if (ctile == ptile) { + return is_native_tile_to_class(connector, ctile); + } + direct_path_iterate(nmap, (struct tile *)ptile, + (struct tile *)ctile, ftile, ttile) { + if (tile_terrain(ttile) == nullptr) { + /* Client: tile is unknown (if known but not seen, + * the result is based on recorded terrains and extras) */ + definite = FALSE; + continue; + } + if (tile_terrain(ftile) + && !is_native_move(nmap, connector, ftile, ttile)) { + return TRI_NO; + } + if (pplayer == nullptr) { + unit_list_iterate(ttile->units, punit) { + if (is_attack_unit(punit) || is_guard_unit(punit)) { + /* Might be a blocker of the path */ + definite = FALSE; + } + } unit_list_iterate_end; + } else { + unit_list_iterate(ttile->units, punit) { + if ((is_attack_unit(punit) || is_guard_unit(punit)) + && !pplayers_allied(pplayer, unit_owner(punit))) { + return TRI_NO; + } + } unit_list_iterate_end; + } + } direct_path_iterate_end; + + return definite ? TRI_YES : TRI_MAYBE; +} + /************************************************************************//** Is there native tile adjacent to given tile ****************************************************************************/ diff --git a/common/movement.h b/common/movement.h index 611b679383..79ded5116c 100644 --- a/common/movement.h +++ b/common/movement.h @@ -28,6 +28,10 @@ struct tile; /* packets.def MOVEFRAGS */ #define MAX_MOVE_FRAGS 65535 +/* Maximal real map distance for path connection. + * TODO: To be unhardcoded some day */ +#define MAX_CONN_DIST 22 + struct unit_type; struct terrain; @@ -95,6 +99,11 @@ bool is_native_move(const struct civ_map *nmap, bool is_native_near_tile(const struct civ_map *nmap, const struct unit_class *uclass, const struct tile *ptile); +enum fc_tristate +uclass_directly_connects(const struct unit_class *connector, + const struct player *pplayer, + const struct tile *ptile, + const struct tile *ctile); bool can_exist_at_tile(const struct civ_map *nmap, const struct unit_type *utype, const struct tile *ptile); diff --git a/common/reqtext.c b/common/reqtext.c index be72a231f4..64f8f1f04c 100644 --- a/common/reqtext.c +++ b/common/reqtext.c @@ -3088,6 +3088,52 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, break; } + case VUT_CONNDIRBY: + if (!preq->source.value.connector) { + break; + } + { + const char *format, *tiletext = NULL; + + if (preq->present) { + /* TRANS: [tile [C]adjacent to ]target tile ... unit class */ + format = _("%s must be connected to the city by a direct %s route,"); + } else { + /* TRANS: [tile [C]adjacent to ]target tile ... unit class */ + format = _("%s must not be connected to the city by a direct %s route."); + } + switch (preq->range) { + case REQ_RANGE_TILE: + /* TRANS: mostly means the center of the target city */ + tiletext = _("Target tile"); + break; + case REQ_RANGE_CADJACENT: + tiletext = _("Target or a cardinally adjacent tile"); + break; + case REQ_RANGE_ADJACENT: + tiletext = _("Target or an adjacent tile"); + break; + case REQ_RANGE_CITY: + case REQ_RANGE_TRADE_ROUTE: + case REQ_RANGE_CONTINENT: + case REQ_RANGE_PLAYER: + case REQ_RANGE_TEAM: + case REQ_RANGE_ALLIANCE: + case REQ_RANGE_WORLD: + case REQ_RANGE_LOCAL: + case REQ_RANGE_COUNT: + /* Not supported. */ + break; + } + if (NULL != tiletext) { + fc_strlcat(buf, prefix, bufsz); + cat_snprintf(buf, bufsz, format, tiletext, + uclass_name_translation(preq->source.value.connector)); + return TRUE; + } + } + break; + case VUT_CITYSTATUS: if (preq->source.value.citystatus != CITYS_LAST) { static char *city_property = NULL; diff --git a/common/requirements.c b/common/requirements.c index 238d4e480a..afacc24911 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -415,6 +415,12 @@ void universal_value_from_str(struct universal *source, const char *value) return; } break; + case VUT_CONNDIRBY: + source->value.connector = unit_class_by_rule_name(value); + if (source->value.connector) { + return; + } + break; case VUT_UCFLAG: source->value.unitclassflag = unit_class_flag_id_by_name(value, fc_strcasecmp); @@ -743,6 +749,12 @@ struct universal universal_by_number(const enum universals_n kind, return source; } break; + case VUT_CONNDIRBY: + source.value.connector = uclass_by_number(value); + if (source.value.connector != NULL) { + return source; + } + break; case VUT_UCFLAG: source.value.unitclassflag = value; return source; @@ -929,6 +941,8 @@ int universal_number(const struct universal *source) return source->value.unitflag; case VUT_UCLASS: return uclass_number(source->value.uclass); + case VUT_CONNDIRBY: + return uclass_number(source->value.connector); case VUT_UCFLAG: return source->value.unitclassflag; case VUT_MINVETERAN: @@ -1103,6 +1117,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_TERRAINCLASS: case VUT_TERRAINALTER: case VUT_CITYTILE: + case VUT_CONNDIRBY: case VUT_MAXTILEUNITS: case VUT_MINLATITUDE: case VUT_MAXLATITUDE: @@ -1267,6 +1282,7 @@ struct requirement req_from_str(const char *type, const char *range, break; case VUT_CITYTILE: case VUT_MAXTILEUNITS: + case VUT_CONNDIRBY: invalid = (req.range != REQ_RANGE_TILE && req.range != REQ_RANGE_CADJACENT && req.range != REQ_RANGE_ADJACENT); @@ -1396,6 +1412,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_MINCITIES: case VUT_MINLATITUDE: case VUT_MAXLATITUDE: + case VUT_CONNDIRBY: /* Most requirements don't support 'survives'. */ invalid = survives; break; @@ -5302,6 +5319,107 @@ is_citystatus_req_active(const struct civ_map *nmap, return TRI_MAYBE; } +/**********************************************************************//** + Determine whether a "ConnectedDirectlyBy" requirement is satisfied + in a given context, ignoring parts of the requirement that can be handled + uniformly for all requirement types. + + context and req must not be nullptr, and req must be a direct connection + requirement +**************************************************************************/ +static enum fc_tristate +is_conndirby_req_active(const struct civ_map *nmap, + const struct req_context *context, + const struct player *other_player, + const struct requirement *req) +{ + const struct unit_class *connector; + const struct player *pplayer = context->player; + const struct city *pcity = context->city; + const struct tile *ptile = context->tile, *ctile; + bool sure = TRUE; + + IS_REQ_ACTIVE_VARIANT_ASSERT(VUT_CONNDIRBY); + + connector = req->source.value.connector; + fc_assert_ret_val(connector, TRI_MAYBE); + + fc_assert_ret_val(req_range_is_valid(req->range), TRI_MAYBE); + + if (ptile == nullptr || pcity == nullptr) { + return TRI_MAYBE; + } + ctile = city_tile(pcity); + if (ctile == nullptr) { + return TRI_MAYBE; + } + + switch (req->range) { + case REQ_RANGE_TILE: + if (real_map_distance(ptile, ctile) > MAX_CONN_DIST) { + return TRI_NO; + } + return uclass_directly_connects(connector, pplayer, ptile, ctile); + /* Following optimizations with stw allow to avoid recheckings + * that are unnecessary because of the transitivity of the function */ + case REQ_RANGE_CADJACENT: + circle_iterate(&(wld.map), ptile, 1, atile) { + const struct tile *stw + = step_towards(&(wld.map), atile, (struct tile *)ctile); + + if (map_distance(ptile, stw) > 1) { + enum fc_tristate conn + = uclass_directly_connects(connector, pplayer, atile, ctile); + + if (real_map_distance(atile, ctile) > MAX_CONN_DIST) { + continue; + } + if (conn == TRI_YES) { + return TRI_YES; + } else if (conn != TRI_NO) { + sure = FALSE; + } + } + } circle_iterate_end; + + return sure ? TRI_NO : TRI_MAYBE; + case REQ_RANGE_ADJACENT: + square_iterate(&(wld.map), ptile, 1, atile) { + const struct tile *stw + = step_towards(&(wld.map), atile, (struct tile *)ctile); + + if (real_map_distance(atile, ctile) > MAX_CONN_DIST) { + continue; + } + if (real_map_distance(ptile, stw) > 1) { + enum fc_tristate conn + = uclass_directly_connects(connector, pplayer, atile, ctile); + + if (conn == TRI_YES) { + return TRI_YES; + } else if (conn != TRI_NO) { + sure = FALSE; + } + } + } square_iterate_end; + + return sure ? TRI_NO : TRI_MAYBE; + case REQ_RANGE_CITY: + case REQ_RANGE_TRADE_ROUTE: + case REQ_RANGE_CONTINENT: + case REQ_RANGE_PLAYER: + case REQ_RANGE_TEAM: + case REQ_RANGE_ALLIANCE: + case REQ_RANGE_WORLD: + case REQ_RANGE_LOCAL: + case REQ_RANGE_COUNT: + fc_assert_msg(FALSE, "Invalid range %d for connection.", req->range); + break; + } + + return TRI_MAYBE; +} + /**********************************************************************//** Determine whether a minimum size requirement is satisfied in a given context, ignoring parts of the requirement that can be handled uniformly @@ -5617,6 +5735,7 @@ static struct req_def req_definitions[VUT_COUNT] = { [VUT_AI_LEVEL] = {is_ai_req_active, REQUCH_HACK}, [VUT_CITYSTATUS] = {is_citystatus_req_active, REQUCH_NO, REQUC_CITYSTATUS}, [VUT_CITYTILE] = {is_citytile_req_active, REQUCH_NO, REQUC_CITYTILE}, + [VUT_CONNDIRBY] = {is_conndirby_req_active, REQUCH_NO}, [VUT_COUNTER] = {is_counter_req_active, REQUCH_NO}, [VUT_DIPLREL] = {is_diplrel_req_active, REQUCH_NO}, [VUT_DIPLREL_TILE] = {is_diplrel_tile_req_active, REQUCH_NO}, @@ -6177,6 +6296,7 @@ bool universal_never_there(const struct universal *source) case VUT_MAXTILEUNITS: case VUT_UTYPE: case VUT_UCLASS: + case VUT_CONNDIRBY: case VUT_MINVETERAN: case VUT_UNITSTATE: case VUT_ACTIVITY: @@ -6790,6 +6910,8 @@ bool are_universals_equal(const struct universal *psource1, return psource1->value.unitflag == psource2->value.unitflag; case VUT_UCLASS: return psource1->value.uclass == psource2->value.uclass; + case VUT_CONNDIRBY: + return psource1->value.connector == psource2->value.connector; case VUT_UCFLAG: return psource1->value.unitclassflag == psource2->value.unitclassflag; case VUT_MINVETERAN: @@ -6940,6 +7062,8 @@ const char *universal_rule_name(const struct universal *psource) return unit_type_flag_id_name(psource->value.unitflag); case VUT_UCLASS: return uclass_rule_name(psource->value.uclass); + case VUT_CONNDIRBY: + return uclass_rule_name(psource->value.connector); case VUT_UCFLAG: return unit_class_flag_id_name(psource->value.unitclassflag); case VUT_MINVETERAN: @@ -7366,6 +7490,12 @@ const char *universal_name_translation(const struct universal *psource, cat_snprintf(buf, bufsz, _("Latitude <= %d"), psource->value.latitude); return buf; + case VUT_CONNDIRBY: + cat_snprintf(buf, bufsz, + /* TRANS: Unit class */ + _("By %s direct path"), + uclass_name_translation(psource->value.connector)); + return buf; case VUT_COUNT: break; } diff --git a/doc/README.effects b/doc/README.effects index af8949a192..1be2ffe2a0 100644 --- a/doc/README.effects +++ b/doc/README.effects @@ -110,6 +110,7 @@ TerrainFlag: Tile, Adjacent, CAdjacent, Traderoute, City TerrainAlter: Tile MinLatitude: Tile, Adjacent, CAdjacent, World MaxLatitude: Tile, Adjacent, CAdjacent, World +ConnectedDirectlyBy: Tile, Adjacent, CAdjacent CityTile: Tile, Adjacent, CAdjacent CityStatus: Traderoute, City Style: Player @@ -130,6 +131,9 @@ CityTile is "Center" (city center), "Claimed" (tile owned by any player), is a port, it's a tile of the nearby ocean but not of its continent). MinLatitude and MaxLatitude are numbers from -1000 (south pole) to 1000 (north pole). +ConnectedDirectlyBy means a unit of the player of given class has + a native "direct" path (specific one among the terrain-ignoring shortest) + from the tile to the city not blocked by a non-allied military unit. CityStatus is "OwnedByOriginal", "Transferred", "Starved", "Disorder", or "Celebration" The difference between "OwnedByOriginal" and "Transferred" is that diff --git a/server/cityturn.c b/server/cityturn.c index bea071e0da..6556472bc8 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1970,6 +1970,7 @@ static bool worklist_item_postpone_req_vec(struct universal *target, case VUT_OTYPE: case VUT_SPECIALIST: case VUT_TERRAINALTER: /* XXX could do this in principle */ + case VUT_CONNDIRBY: /* Will only happen with a bogus ruleset. */ log_error("worklist_change_build_target() has bogus preq"); break; diff --git a/server/ruleset/rssanity.c b/server/ruleset/rssanity.c index 6cf43712bb..61cde5acf9 100644 --- a/server/ruleset/rssanity.c +++ b/server/ruleset/rssanity.c @@ -446,6 +446,7 @@ static bool sanity_check_req_set(rs_conversion_logger logger, case VUT_NATIONALITY: case VUT_MINCULTURE: case VUT_ACHIEVEMENT: + case VUT_CONNDIRBY: case VUT_DIPLREL: case VUT_DIPLREL_TILE: case VUT_DIPLREL_TILE_O: diff --git a/tools/ruledit/univ_value.c b/tools/ruledit/univ_value.c index 0904a659d6..81e32f330f 100644 --- a/tools/ruledit/univ_value.c +++ b/tools/ruledit/univ_value.c @@ -90,6 +90,12 @@ bool universal_value_initial(struct universal *src) } src->value.uclass = uclass_by_number(0); return TRUE; + case VUT_CONNDIRBY: + if (game.control.num_unit_classes <= 0) { + return FALSE; + } + src->value.connector = uclass_by_number(0); + return TRUE; case VUT_UCFLAG: src->value.unitclassflag = (enum unit_class_flag_id)0; return TRUE; @@ -311,6 +317,11 @@ void universal_kind_values(struct universal *univ, cb(uclass_rule_name(pclass), univ->value.uclass == pclass, data); } unit_class_re_active_iterate_end; break; + case VUT_CONNDIRBY: + unit_class_re_active_iterate(pclass) { + cb(uclass_rule_name(pclass), univ->value.connector == pclass, data); + } unit_class_re_active_iterate_end; + break; case VUT_OTYPE: output_type_iterate(otype) { cb(get_output_name(otype), univ->value.outputtype == otype, data);