From 14f6be479c1e78d41a0cee3f8a2eaf6064c1c066 Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Sat, 12 Nov 2022 01:03:17 +0300 Subject: [PATCH] Implement direct connectivity "ConnectedDirectlyBy" requirement states that given unit class can pass from target tile to target city by a native path not blocked by a military unit not allied to target player. The "direct" path goes on rectangular map diagonally as long as the city is not in a cardinal direction. See OSDN#46051 Signed-off-by: Ihnatus --- ai/default/daicity.c | 1 + ai/default/daieffects.c | 1 + common/fc_types.h | 3 + common/map.c | 37 +++++++++ common/map.h | 21 ++++- common/metaknowledge.c | 163 ++++++++++++++++++++++++++++++++++++- common/movement.c | 57 +++++++++++++ common/movement.h | 9 ++ common/reqtext.c | 43 ++++++++++ common/requirements.c | 128 +++++++++++++++++++++++++++++ doc/README.effects | 4 + server/cityturn.c | 1 + server/rssanity.c | 1 + tools/ruledit/univ_value.c | 11 +++ 14 files changed, 478 insertions(+), 2 deletions(-) diff --git a/ai/default/daicity.c b/ai/default/daicity.c index 632040f340..ca2fe13c62 100644 --- a/ai/default/daicity.c +++ b/ai/default/daicity.c @@ -2062,6 +2062,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 c59d56c4f1..db8b191b97 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -893,6 +893,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/common/fc_types.h b/common/fc_types.h index 360708804a..b502711ad5 100644 --- a/common/fc_types.h +++ b/common/fc_types.h @@ -660,6 +660,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; @@ -816,6 +817,8 @@ typedef union { #define SPECENUM_VALUE51NAME "Counter" #define SPECENUM_VALUE52 VUT_ORIGINAL_OWNER #define SPECENUM_VALUE52NAME "OriginalOwner" +#define SPECENUM_VALUE54 VUT_CONNDIRBY +#define SPECENUM_VALUE54NAME "ConnectedDirectlyBy" /* Keep this last. */ #define SPECENUM_COUNT VUT_COUNT diff --git a/common/map.c b/common/map.c index ead081d9be..d396078913 100644 --- a/common/map.c +++ b/common/map.c @@ -1388,6 +1388,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 0198cc9c77..5c109f95d1 100644 --- a/common/map.h +++ b/common/map.h @@ -74,7 +74,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); @@ -538,6 +540,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 4560a4fb60..d2e18e47c3 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. @@ -120,6 +125,87 @@ static bool is_tile_seen_traderoute(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; + + 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(&(wld.map), 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(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_military_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. @@ -435,6 +521,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_TRADEROUTE: + 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 8a00f0b881..e53144d9e9 100644 --- a/common/movement.c +++ b/common/movement.c @@ -455,6 +455,63 @@ bool is_native_move(const struct unit_class *punitclass, 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; + + if (ctile == ptile) { + return is_native_tile_to_class(connector, ctile); + } + direct_path_iterate(&(wld.map), (struct tile *)ptile, + (struct tile *)ctile, ftile, ttile) { + if (!tile_terrain(ttile)) { + /* 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(connector, ftile, ttile)) { + return TRI_NO; + } + if (!pplayer) { + unit_list_iterate(ttile->units, punit) { + if (is_military_unit(punit)) { + /* Might be a blocker of the path */ + definite = FALSE; + } + } unit_list_iterate_end; + } else { + unit_list_iterate(ttile->units, punit) { + if (is_military_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 3daf8e62ce..1ad7c5b269 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; @@ -93,6 +97,11 @@ bool is_native_move(const struct unit_class *punitclass, 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 ffc1593dd5..f4d943675e 100644 --- a/common/reqtext.c +++ b/common/reqtext.c @@ -2969,6 +2969,49 @@ 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"); + case REQ_RANGE_CADJACENT: + tiletext = _("Target or a cardinally adjacent tile"); + case REQ_RANGE_ADJACENT: + tiletext = _("Target or an adjacent tile"); + case REQ_RANGE_CITY: + case REQ_RANGE_TRADEROUTE: + 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 4174c865a2..627c9777c5 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -393,6 +393,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); @@ -691,6 +697,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; @@ -861,6 +873,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: @@ -1025,6 +1039,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: @@ -1176,6 +1191,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); @@ -1286,6 +1302,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_MINTECHS: case VUT_MINLATITUDE: case VUT_MAXLATITUDE: + case VUT_CONNDIRBY: /* Most requirements don't support 'survives'. */ invalid = survives; break; @@ -4533,6 +4550,105 @@ is_citystatus_req_active(const struct req_context *context, 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 null, and req must be a direct connection + requirement +**************************************************************************/ +static enum fc_tristate +is_conndirby_req_active(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 == NULL || pcity == NULL) { + return TRI_MAYBE; + } + ctile = city_tile(pcity); + if (ctile == NULL) { + 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_TRADEROUTE: + 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 @@ -4816,6 +4932,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}, @@ -5346,6 +5463,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: @@ -5942,6 +6060,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: @@ -6078,6 +6198,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: @@ -6464,6 +6586,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 01bc38d386..45a3314ae6 100644 --- a/doc/README.effects +++ b/doc/README.effects @@ -97,6 +97,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 @@ -117,6 +118,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", "Starved", "Disorder", or "Celebration" DiplRel is a diplomatic relationship. MaxUnitsOnTile is about the number of units present on a tile. diff --git a/server/cityturn.c b/server/cityturn.c index 7e2c804d9a..75eb23c9ab 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1827,6 +1827,7 @@ static bool worklist_item_postpone_req_vec(struct universal *target, case VUT_SPECIALIST: case VUT_TERRAINALTER: /* XXX could do this in principle */ case VUT_CITYSTATUS: + case VUT_CONNDIRBY: /* Will only happen with a bogus ruleset. */ log_error("worklist_change_build_target() has bogus preq"); break; diff --git a/server/rssanity.c b/server/rssanity.c index ebcb2b221a..c01c547698 100644 --- a/server/rssanity.c +++ b/server/rssanity.c @@ -422,6 +422,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 4ddcbe52ba..a844062955 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; @@ -293,6 +299,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); -- 2.34.1