From d1c47bb616de0a6571de044d25c6b16c19f8f6b3 Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Thu, 27 Oct 2022 00:35:10 +0300 Subject: [PATCH] Add continent control to "CityTile" reqs "Same Continent" checks the tile's continent relatively to city center, "Bordering TClass Region" checks it towards any tile around city center that is of a different terrain class from it. Introduces "need/have_different_terrainclass" causes for "can't be built" signals. Fixes in process that "CityTile", "CityCenter" without a city specified is considered unknowable at RPT_CERTAIN metaknowledge.c; also, detects contradiction of "CityTile", "CityCenter" and "Building" at "Tile" range. See OSDN#45907 Signed-off-by: Ihnatus --- ai/default/daieffects.c | 13 ++- common/fc_types.h | 4 + common/metaknowledge.c | 19 +++ common/reqtext.c | 26 +++++ common/requirements.c | 250 +++++++++++++++++++++++++++++++++++++++- doc/README.effects | 20 ++-- server/cityturn.c | 29 ++++- 7 files changed, 349 insertions(+), 12 deletions(-) diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index 6b77f44c3e..b4b2b71723 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -784,6 +784,18 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, return city_owner(pcity) != pcity->original; } + + case VUT_CITYTILE: + if (NULL != pcity + && (CITYT_BORDERING_TCLASS_REGION == preq->source.value.citytile + || CITYT_SAME_CONTINENT == preq->source.value.citytile)) { + /* We should test if an ocean is nearby or it is an 1-tile island, + * and then tell if we can alterate the terrain... */ + } else { + /* Probably, can always be met */ + return TRUE; + } + fc__fallthrough; case VUT_TERRAIN: case VUT_TERRAINCLASS: case VUT_TERRAINALTER: @@ -823,7 +835,6 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, return FALSE; case VUT_OTYPE: - case VUT_CITYTILE: case VUT_IMPR_GENUS: /* Can always be achieved. */ return TRUE; diff --git a/common/fc_types.h b/common/fc_types.h index 57ee66479e..7db08badd7 100644 --- a/common/fc_types.h +++ b/common/fc_types.h @@ -536,6 +536,10 @@ const char *ai_level_name_update_cb(const char *old); #define SPECENUM_VALUE2NAME "Extras Owned" #define SPECENUM_VALUE3 CITYT_WORKED #define SPECENUM_VALUE3NAME "Worked" +#define SPECENUM_VALUE4 CITYT_SAME_CONTINENT +#define SPECENUM_VALUE4NAME "Same Continent" +#define SPECENUM_VALUE5 CITYT_BORDERING_TCLASS_REGION +#define SPECENUM_VALUE5NAME "Bordering TClass Region" #define SPECENUM_COUNT CITYT_LAST #include "specenum_gen.h" diff --git a/common/metaknowledge.c b/common/metaknowledge.c index 687cf0a041..8010aa8412 100644 --- a/common/metaknowledge.c +++ b/common/metaknowledge.c @@ -358,6 +358,25 @@ static bool is_req_knowable(const struct player *pov_player, * RPT_POSSIBLE. */ return prob_type == RPT_CERTAIN; } + if (context->city == NULL) { + switch (req->source.value.citytile) { + case CITYT_CENTER: + case CITYT_SAME_CONTINENT: + case CITYT_BORDERING_TCLASS_REGION: + /* Require the city, not passed */ + return prob_type == RPT_CERTAIN; + case CITYT_CLAIMED: + case CITYT_WORKED: + case CITYT_EXTRAS_OWNED: + /* Do not require a city passed */ + break; + case CITYT_LAST: + /* Invalid */ + fc_assert_msg(req->source.value.citytile != CITYT_LAST, + "Invalid city tile property."); + return FALSE; + } + } switch (req->range) { case REQ_RANGE_TILE: diff --git a/common/reqtext.c b/common/reqtext.c index 8e81381183..ffc1593dd5 100644 --- a/common/reqtext.c +++ b/common/reqtext.c @@ -33,6 +33,7 @@ #include "requirements.h" #include "server_settings.h" #include "specialist.h" +#include "terrain.h" #include "reqtext.h" @@ -2883,6 +2884,31 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, case CITYT_WORKED: tile_property = _("worked tiles"); break; + case CITYT_SAME_CONTINENT: + /* TRANS: a specific city for each use case */ + tile_property = _("tiles on the same continent as the city"); + break; + case CITYT_BORDERING_TCLASS_REGION: + { + bool oceanic_cities = FALSE; /* FIXME: maybe cache globally? */ + + terrain_type_iterate(tt) { + if (terrain_type_terrain_class(tt) == TC_OCEAN + && !terrain_has_flag(tt, TER_NO_CITIES)) { + oceanic_cities = TRUE; + /* TRANS: a specific city for each use case */ + tile_property = _("tiles of a mass of a different " + "terrain class next to the city"); + break; + } + } terrain_type_iterate_end; + if (oceanic_cities) { + break; + } + /* TRANS: a specific city for each use case */ + tile_property = _("tiles of a body of water next to the city"); + } + break; case CITYT_LAST: fc_assert(preq->source.value.citytile != CITYT_LAST); break; diff --git a/common/requirements.c b/common/requirements.c index e55039613a..cf5b7f6908 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -1273,6 +1273,47 @@ static bool nation_contra_group(const struct requirement *nation_req, return FALSE; } +/**********************************************************************//** + Returns TRUE if the specified city center or continent requirement + contradicts the other city tile requirement. +**************************************************************************/ +static bool city_center_contra(const struct requirement *cc_req, + const struct requirement *ct_req) +{ + /* The input is sane. */ + fc_assert_ret_val(cc_req->source.kind == VUT_CITYTILE + && ct_req->source.kind == VUT_CITYTILE, FALSE); + + if (cc_req->source.value.citytile == CITYT_CENTER + && cc_req->present && cc_req->range <= ct_req->range) { + switch (ct_req->source.value.citytile) { + case CITYT_CENTER: + case CITYT_CLAIMED: + case CITYT_EXTRAS_OWNED: + case CITYT_WORKED: + case CITYT_SAME_CONTINENT: + /* Should be always on city center */ + return !ct_req->present; + case CITYT_BORDERING_TCLASS_REGION: + /* Handled later */ + break; + case CITYT_LAST: + /* Error */ + fc_assert_ret_val(ct_req->source.value.citytile != CITYT_LAST, FALSE); + } + } else if ((cc_req->source.value.citytile == CITYT_SAME_CONTINENT + || cc_req->source.value.citytile == CITYT_CENTER) + && ct_req->source.value.citytile + == CITYT_BORDERING_TCLASS_REGION + && REQ_RANGE_TILE == cc_req->range + && REQ_RANGE_TILE == ct_req->range) { + /* Can't coexist */ + return cc_req->present ? ct_req->present : !ct_req->present; + } + + return FALSE; +} + /**********************************************************************//** Returns TRUE if req1 and req2 contradicts each other because a present requirement implies the presence of a !present requirement according to @@ -1339,6 +1380,13 @@ bool are_requirements_contradictions(const struct requirement *req1, case VUT_IMPROVEMENT: if (req2->source.kind == VUT_IMPR_GENUS) { return impr_contra_genus(req1, req2); + } else if (req2->source.kind == VUT_CITYTILE + && req2->source.value.citytile == CITYT_CENTER + && REQ_RANGE_TILE == req2->range + && REQ_RANGE_TILE == req1->range + && req1->present){ + /* A building must be in a city */ + return !req2->present; } /* No special knowledge. */ @@ -1463,6 +1511,20 @@ bool are_requirements_contradictions(const struct requirement *req1, /* No special knowledge. */ return FALSE; break; + case VUT_CITYTILE: + if (req2->source.kind == VUT_CITYTILE) { + return city_center_contra(req1, req2) + || city_center_contra(req2, req1); + } else if (req1->source.value.citytile == CITYT_CENTER + && req2->source.kind == VUT_IMPROVEMENT + && REQ_RANGE_TILE == req2->range + && REQ_RANGE_TILE == req1->range + && req2->present){ + /* A building must be in a city */ + return !req1->present; + } + + return FALSE; default: /* No special knowledge exists. The requirements aren't the exact * opposite of each other per the initial check. */ @@ -3205,6 +3267,185 @@ static enum fc_tristate is_citytile_in_range(const struct tile *target_tile, break; } + return TRI_MAYBE; + case CITYT_SAME_CONTINENT: + { + Continent_id cc; + + if (!target_city) { + return TRI_MAYBE; + } + cc = tile_continent(city_tile(target_city)); + /* Note: No special treatment of 0 == cc here*/ + switch (range) { + case REQ_RANGE_TILE: + return BOOL_TO_TRISTATE(tile_continent(target_tile) == cc); + case REQ_RANGE_CADJACENT: + if (tile_continent(target_tile) == cc) { + return TRI_YES; + } + cardinal_adjc_iterate(&(wld.map), target_tile, adjc_tile) { + if (tile_continent(adjc_tile) == cc) { + return TRI_YES; + } + } cardinal_adjc_iterate_end; + + return TRI_NO; + case REQ_RANGE_ADJACENT: + if (tile_continent(target_tile) == cc) { + return TRI_YES; + } + adjc_iterate(&(wld.map), target_tile, adjc_tile) { + if (tile_continent(adjc_tile) == cc) { + return TRI_YES; + } + } adjc_iterate_end; + + return TRI_NO; + 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 citytile.", range); + break; + } + } + + return TRI_MAYBE; + case CITYT_BORDERING_TCLASS_REGION: + { + int n = 0; + Continent_id adjc_cont[8], cc; + bool ukt = FALSE; + + if (!target_city) { + return TRI_MAYBE; + } + cc = tile_continent(city_tile(target_city)); + if (!cc) { + /* Don't know the city center terrain class. + * Maybe, the city floats? Even if the rules prohibit it... */ + return TRI_MAYBE; + } + adjc_iterate(&(wld.map), city_tile(target_city), adjc_tile) { + Continent_id tc = tile_continent(adjc_tile); + + if (0 != tc) { + bool seen = FALSE; + int i = n; + + if (tc == cc) { + continue; + } + while (--i >= 0) { + if (adjc_cont[i] == tc) { + seen = TRUE; + break; + } + } + if (seen) { + continue; + } + adjc_cont[n++] = tile_continent(adjc_tile); + } else { + /* Likely, it's a black tile in client and we don't know + * We possibly can calculate, but keep it simple. */ + ukt = TRUE; + } + } adjc_iterate_end; + if (0 == n) { + return ukt ? TRI_MAYBE : TRI_NO; + } + + switch (range) { + case REQ_RANGE_TILE: + { + Continent_id tc = tile_continent(target_tile); + + if (cc == tc) { + return TRI_NO; + } + if (0 == tc || ukt) { + return TRI_MAYBE; + } + for (int i = 0; i < n; i++) { + if (tc == adjc_cont[i]) { + return TRI_YES; + } + } + } + + return TRI_NO; + case REQ_RANGE_ADJACENT: + if (ukt) { + /* If ALL the tiles in range are on cc, we can say it's false */ + square_iterate(&(wld.map), target_tile, 1, adjc_tile) { + if (tile_continent(adjc_tile) != cc) { + return TRI_MAYBE; + } + } square_iterate_end; + + return TRI_NO; + } else { + square_iterate(&(wld.map), target_tile, 1, adjc_tile) { + Continent_id tc = tile_continent(adjc_tile); + + if (0 == tc) { + return TRI_MAYBE; + } + for (int i = 0; i < n; i++) { + if (tc == adjc_cont[i]) { + return TRI_YES; + } + } + } square_iterate_end; + } + + return TRI_NO; + case REQ_RANGE_CADJACENT: + /* Do the same in a 5x5 square without corners (adjc of cadjc) */ + if (ukt) { + /* If ALL the tiles in range are on cc, we can say it's false */ + circle_iterate(&(wld.map), target_tile, 1, cadjc_tile) { + if (tile_continent(cadjc_tile) != cc) { + return TRI_MAYBE; + } + } circle_iterate_end; + } else { + circle_iterate(&(wld.map), target_tile, 1, cadjc_tile) { + Continent_id tc = tile_continent(cadjc_tile); + + if (0 == tc) { + return TRI_MAYBE; + } + for (int i = 0; i < n; i++) { + if (tc == adjc_cont[i]) { + return TRI_YES; + } + } + } circle_iterate_end; + } + + return TRI_NO; + 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 citytile.", range); + break; + } + } + return TRI_MAYBE; case CITYT_LAST: /* Handled below */ @@ -3975,7 +4216,7 @@ bool is_req_unchanging(const struct requirement *req) case VUT_OTYPE: case VUT_SPECIALIST: /* Only so long as it's at local range only */ case VUT_AI_LEVEL: - case VUT_CITYTILE: + case VUT_CITYTILE: /* FIXME: actually, some terrain alterations are easy */ case VUT_CITYSTATUS: /* We don't *want* owner of our city to change */ case VUT_STYLE: case VUT_TOPO: @@ -5204,6 +5445,13 @@ const char *universal_name_translation(const struct universal *psource, case CITYT_WORKED: fc_strlcat(buf, _("Worked tile"), bufsz); break; + case CITYT_SAME_CONTINENT: + fc_strlcat(buf, _("Same continent tile"), bufsz); + break; + case CITYT_BORDERING_TCLASS_REGION: + /* TRANS: Short for "a tile of other terrain class mass near city" */ + fc_strlcat(buf, _("Port reachable tile"), bufsz); + break; case CITYT_LAST: fc_assert(psource->value.citytile != CITYT_LAST); fc_strlcat(buf, "error", bufsz); diff --git a/doc/README.effects b/doc/README.effects index cff9323eb6..6f9ae94651 100644 --- a/doc/README.effects +++ b/doc/README.effects @@ -111,26 +111,28 @@ MinSize is the minimum size of a city required. AI is ai player difficulty level. TerrainClass is either "Land" or "Oceanic". TerrainAlter is "CanIrrigate", "CanMine", "CanRoad", "CanBase", or "CanPlace" -CityTile is "Center" (city center), "Claimed" (tile owned), -"Extras Owned" (extra on tile owned), or "Worked" (worked by any city) +CityTile is "Center" (city center), "Claimed" (tile owned by any city), + "Extras Owned" (extra on tile owned), "Worked" (worked by any city), + "Same Continent" (as the city center) or "Bordering TClass Region" (if the city + 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). + (north pole). CityStatus is "OwnedByOriginal", "Starved", "Disorder", or "Celebration" DiplRel is a diplomatic relationship. MaxUnitsOnTile is about the number of units present on a tile. UnitState is "Transported", "Transporting", "OnNativeTile", "OnLivableTile", -"InNativeExtra", "MovedThisTurn" or "HasHomeCity". + "InNativeExtra", "MovedThisTurn" or "HasHomeCity". Activity is "Idle", "Pollution", "Mine", "Irrigate", "Fortified", -"Pillage", "Transform", "Fortifying", "Fallout", -"Base", "Road", "Convert", "Cultivate", or "Plant". + "Pillage", "Transform", "Fortifying", "Fallout", + "Base", "Road", "Convert", "Cultivate", or "Plant". MinMoveFrags is the minimum move fragments the unit must have left. MinCalFrag is the minimum sub-year division the calendar must have reached, -if enabled (see [calendar].fragments in game.ruleset). + if enabled (see [calendar].fragments in game.ruleset). Nationality is fulfilled by any citizens of the given nationality -present in the city. + present in the city. OriginalOwner is the city founding nation ServerSetting is if a Boolean server setting is enabled. The setting must be -visible to all players and affect the game rules. + visible to all players and affect the game rules. Effect types ============ diff --git a/server/cityturn.c b/server/cityturn.c index 470b567664..726873152e 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1781,6 +1781,34 @@ static bool worklist_item_postpone_req_vec(struct universal *target, purge = TRUE; break; + case VUT_CITYTILE: + if (CITYT_BORDERING_TCLASS_REGION == preq->source.value.citytile + && (preq->range == REQ_RANGE_CADJACENT + || preq->range == REQ_RANGE_ADJACENT)) { + if (preq->present) { + notify_player(pplayer, city_tile(pcity), + E_CITY_CANTBUILD, ftc_server, + _("%s can't build %s from the worklist; " + "different terrain class nearby is required. " + "Postponing..."), + city_link(pcity), + tgt_name); + script_server_signal_emit(signal_name, ptarget, + pcity, "need_different_terrainclass"); + } else { + notify_player(pplayer, city_tile(pcity), + E_CITY_CANTBUILD, ftc_server, + _("%s can't build %s from the worklist; " + "different terrain class nearby is prohibited. " + "Postponing..."), + city_link(pcity), + tgt_name); + script_server_signal_emit(signal_name, ptarget, + pcity, "have_different_terrainclass"); + } + break; + } + fc__fallthrough; case VUT_UTYPE: case VUT_UTFLAG: case VUT_UCLASS: @@ -1794,7 +1822,6 @@ 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_CITYTILE: case VUT_CITYSTATUS: /* Will only happen with a bogus ruleset. */ log_error("worklist_change_build_target() has bogus preq"); -- 2.34.1