From 3f36afcfee1c013cf5c75e8a769596eac33f73ab Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Sat, 5 Nov 2022 20:36:41 +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/aitools.c | 1 + ai/default/daieffects.c | 4 +- common/fc_types.h | 4 + common/metaknowledge.c | 20 +++ common/reqtext.c | 26 ++++ common/requirements.c | 268 +++++++++++++++++++++++++++++++++++++++- doc/README.effects | 20 +-- server/cityturn.c | 30 ++++- 8 files changed, 358 insertions(+), 15 deletions(-) diff --git a/ai/default/aitools.c b/ai/default/aitools.c index 12348181fe..49fb471424 100644 --- a/ai/default/aitools.c +++ b/ai/default/aitools.c @@ -1391,6 +1391,7 @@ bool dai_cant_help_req(const struct req_context *context, case VUT_TERRAINCLASS: case VUT_TERRFLAG: case VUT_TERRAINALTER: + case VUT_CITYTILE: return !is_req_active(context, NULL, req, RPT_POSSIBLE); default: return is_req_preventing(context, NULL, req) > REQUCH_NO; diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index 6b77f44c3e..640dfe5dd3 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -775,6 +775,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, return FALSE; case VUT_CITYSTATUS: + /* FIXME: update */ if (pcity == NULL) { return preq->present; } @@ -813,6 +814,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, /* No way to add once lost. */ return !preq->present; + case VUT_NATION: case VUT_NATIONGROUP: case VUT_AI_LEVEL: @@ -823,7 +825,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; @@ -843,6 +844,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, case VUT_STYLE: case VUT_UNITSTATE: case VUT_ACTIVITY: + case VUT_CITYTILE: case VUT_MINMOVES: case VUT_MINVETERAN: case VUT_MINHP: diff --git a/common/fc_types.h b/common/fc_types.h index be787f72a8..360708804a 100644 --- a/common/fc_types.h +++ b/common/fc_types.h @@ -546,6 +546,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..4560a4fb60 100644 --- a/common/metaknowledge.c +++ b/common/metaknowledge.c @@ -353,6 +353,26 @@ static bool is_req_knowable(const struct player *pov_player, if (req->source.kind == VUT_CITYTILE) { struct city *pcity; + 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; + } + } + if (context->tile == NULL) { /* The tile may exist but not be passed when the problem type is * RPT_POSSIBLE. */ 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 9fc2e758ac..149045febc 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -133,8 +133,7 @@ static enum req_unchanging_status #define REQUC_NALLY unchanging_noally /**********************************************************************//** - Special CityTile case handler. Currently, only city center requirement - depends on the city, but other ones are fulfilled on (C)Adjacent tiles + Special CityTile case handler **************************************************************************/ static enum req_unchanging_status unchanging_citytile(enum req_unchanging_status def, @@ -143,11 +142,12 @@ static enum req_unchanging_status { fc_assert_ret_val(VUT_CITYTILE == req->source.kind, REQUCH_NO); if (CITYT_CENTER == req->source.value.citytile - || (NULL != context->city && NULL != context->tile + || (CITYT_BORDERING_TCLASS_REGION != req->source.value.citytile + && NULL != context->city && NULL != context->tile && NULL != city_tile(context->city) && are_tiles_in_range(city_tile(context->city), context->tile, req->range))){ - /* Cities don't move */ + /* Cities don't move, and most reqs are present on city center */ return REQUCH_YES; } return def; @@ -1432,6 +1432,48 @@ 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); + } + } + 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 @@ -1498,6 +1540,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. */ @@ -1622,6 +1671,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. */ @@ -4074,6 +4137,196 @@ is_citytile_req_active(const struct req_context *context, break; } + return TRI_MAYBE; + case CITYT_SAME_CONTINENT: + { + Continent_id cc; + const struct tile *target_tile = context->tile, *cc_tile; + + if (!context->city) { + return TRI_MAYBE; + } + cc_tile = city_tile(context->city); + if (!cc_tile) { + /* Unplaced virtual city */ + return TRI_MAYBE; + } + cc = tile_continent(cc_tile); + /* Note: No special treatment of 0 == cc here*/ + switch (req->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.", req->range); + break; + } + } + + return TRI_MAYBE; + case CITYT_BORDERING_TCLASS_REGION: + { + int n = 0; + Continent_id adjc_cont[8], cc; + bool ukt = FALSE; + const struct tile *target_tile = context->tile, *cc_tile; + + if (!context->city) { + return TRI_MAYBE; + } + cc_tile = city_tile(context->city); + if (!cc_tile) { + /* Unplaced virtual city */ + return TRI_MAYBE; + } + cc = tile_continent(cc_tile); + 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), cc_tile, 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 (req->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: + 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.", req->range); + break; + } + } + return TRI_MAYBE; case CITYT_LAST: /* Handled below */ @@ -5921,6 +6174,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 9bf11756c2..fc143417f3 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 player), + "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 3a8fc62f9e..7e2c804d9a 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1783,6 +1783,35 @@ static bool worklist_item_postpone_req_vec(struct universal *target, /* Can't change where the city is located. */ 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; + } + /* Other values should not present in build reqs */ + fc__fallthrough; case VUT_UTYPE: case VUT_UTFLAG: @@ -1797,7 +1826,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