From 1536cd5d318a92e590b88b349cc1aafae279ffdf Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Thu, 10 Nov 2022 15:10:56 +0300 Subject: [PATCH] Update, improve and split is_req_unchanging() Certain source kinds treatment was changed because of previous changes or just strange behaviour, mainly to postpone city worklists instead of purging when building a thing ever seems theoretically possible. AI gets some source kings as unchanging by legacy. See OSDN#46029 Signed-off-by: Ihnatus --- ai/default/daicity.c | 88 +++++++- ai/default/daicity.h | 5 + common/city.c | 6 +- common/improvement.c | 3 +- common/requirements.c | 501 ++++++++++++++++++++++++++++++------------ common/requirements.h | 28 ++- common/research.c | 3 +- server/rssanity.c | 5 +- 8 files changed, 485 insertions(+), 154 deletions(-) diff --git a/ai/default/daicity.c b/ai/default/daicity.c index 5006610a9d..8cb51e1a5f 100644 --- a/ai/default/daicity.c +++ b/ai/default/daicity.c @@ -1897,7 +1897,8 @@ static bool should_force_recalc(struct city *pcity) return city_built_last_turn(pcity) || (VUT_IMPROVEMENT == pcity->production.kind && !improvement_has_flag(pcity->production.value.building, IF_GOLD) - && !can_city_build_improvement_later(pcity, pcity->production.value.building)); + && !dai_can_city_build_improvement_later + (pcity, pcity->production.value.building)); } /**********************************************************************//** @@ -2044,6 +2045,91 @@ void dai_consider_wonder_city(struct ai_type *ait, struct city *pcity, bool *res } } +/**********************************************************************//** + Default AI does not know ways of ever fulfilling this requirement + and should not think about it +**************************************************************************/ +static bool dai_cant_help_req(const struct req_context *context, + const struct requirement *req) +{ + switch (req->source.kind) { + /* Unskilled in channel digging and merchantry */ + case VUT_TERRAIN: + case VUT_EXTRA: + case VUT_GOOD: + case VUT_TERRAINCLASS: + case VUT_TERRFLAG: + case VUT_TERRAINALTER: + return !is_req_active(context, NULL, req, RPT_POSSIBLE); + default: + return is_req_preventing(context, NULL, req, RPT_POSSIBLE) > REQUCH_NO; + } +} + +/**********************************************************************//** + Whether AI expects to be ever able to build given building in the city +**************************************************************************/ +bool dai_can_city_build_improvement_later(const struct city *pcity, + const struct impr_type *pimprove) +{ + const struct req_context city_ctxt = { + .player = city_owner(pcity), + .city = pcity, + .tile = city_tile(pcity), + }; + + /* FIXME: AI may be too stupid to sell obsoleting improvements + * from the city that are _not_ checked here. */ + if (!dai_can_player_build_improvement_later(city_owner(pcity), + pimprove)) { + return FALSE; + } + + /* Check for requirements that aren't met and that are unchanging (so + * they can never be met). */ + requirement_vector_iterate(&pimprove->reqs, preq) { + if (dai_cant_help_req(&city_ctxt, preq)) { + return FALSE; + } + } requirement_vector_iterate_end; + + return TRUE; +} + +/**********************************************************************//** + Whether AI expects to be ever able to build given building +**************************************************************************/ +bool +dai_can_player_build_improvement_later(const struct player *p, + const struct impr_type *pimprove) +{ + const struct req_context context = { .player = p }; + + if (!valid_improvement(pimprove)) { + return FALSE; + } + if (improvement_obsolete(p, pimprove, NULL)) { + return FALSE; + } + if (is_great_wonder(pimprove) && !great_wonder_is_available(pimprove)) { + /* Can't build wonder if already built */ + return FALSE; + } + + /* Check for requirements that aren't met and that are unchanging (so + * they can never be met). */ + requirement_vector_iterate(&pimprove->reqs, preq) { + if (preq->range >= REQ_RANGE_PLAYER + && dai_cant_help_req(&context, preq)) { + return FALSE; + } + } requirement_vector_iterate_end; + /* FIXME: should check some "unchanging" reqs here - like if there's + * a nation requirement, we can go ahead and check it now. */ + + return TRUE; +} + /**********************************************************************//** Returns a buildable, non-obsolete building that can provide the effect. diff --git a/ai/default/daicity.h b/ai/default/daicity.h index f0e175ea2b..f54f0b6ce0 100644 --- a/ai/default/daicity.h +++ b/ai/default/daicity.h @@ -100,6 +100,11 @@ void dai_build_adv_adjust(struct ai_type *ait, struct player *pplayer, void dai_consider_wonder_city(struct ai_type *ait, struct city *pcity, bool *result); +bool dai_can_city_build_improvement_later(const struct city *pcity, + const struct impr_type *pimprove); +bool +dai_can_player_build_improvement_later(const struct player *p, + const struct impr_type *pimprove); Impr_type_id dai_find_source_building(struct city *pcity, enum effect_type effect_type, const struct unit_type *utype); diff --git a/common/city.c b/common/city.c index 7b07649f45..8b13bc9b44 100644 --- a/common/city.c +++ b/common/city.c @@ -859,6 +859,9 @@ bool can_city_build_improvement_later(const struct city *pcity, }; /* Can the _player_ ever build this improvement? */ + /* NOTE: It checks for obsoletion player-level. What aboult checking + * for it city-level? That may unlist from a worklist some things + * we'll be able to switch to after e.g. selling something else */ if (!can_player_build_improvement_later(city_owner(pcity), pimprove)) { return FALSE; } @@ -866,8 +869,7 @@ bool can_city_build_improvement_later(const struct city *pcity, /* Check for requirements that aren't met and that are unchanging (so * they can never be met). */ requirement_vector_iterate(&pimprove->reqs, preq) { - if (is_req_unchanging(preq) - && !is_req_active(&city_ctxt, NULL, preq, RPT_POSSIBLE)) { + if (is_req_preventing(&city_ctxt, NULL, preq, RPT_POSSIBLE)) { return FALSE; } } requirement_vector_iterate_end; diff --git a/common/improvement.c b/common/improvement.c index 3761a67fcc..47784d7942 100644 --- a/common/improvement.c +++ b/common/improvement.c @@ -750,8 +750,7 @@ bool can_player_build_improvement_later(const struct player *p, * they can never be met). */ requirement_vector_iterate(&pimprove->reqs, preq) { if (preq->range >= REQ_RANGE_PLAYER - && is_req_unchanging(preq) - && !is_req_active(&context, NULL, preq, RPT_POSSIBLE)) { + && is_req_preventing(&context, NULL, preq, RPT_POSSIBLE)) { return FALSE; } } requirement_vector_iterate_end; diff --git a/common/requirements.c b/common/requirements.c index 8458721bd0..7dd905ee64 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -48,6 +48,170 @@ typedef enum req_item_found (*universal_found)(const struct requirement *, const struct universal *); static universal_found universal_found_function[VUT_COUNT] = {NULL}; +static +enum fc_tristate tri_req_present(const struct req_context *context, + const struct player *other_player, + const struct requirement *req); + +/* Function pointer for requirement-type-specific is_req_active handlers */ +typedef enum fc_tristate +(*is_req_active_cb)(const struct req_context *context, + const struct player *other_player, + const struct requirement *req); + +static inline bool are_tiles_in_range(const struct tile *tile1, + const struct tile *tile2, + enum req_range range); + +/**********************************************************************//** + Never changes in local range + Mostly it's about requirements evaluated from constants and persistent + ruleset objects passed in the context. +**************************************************************************/ +static enum req_unchanging_status + unchanging_local(enum req_unchanging_status def, + const struct req_context *context, + const struct requirement *req) +{ + return req->range == REQ_RANGE_LOCAL ? REQUCH_YES : def; +} +#define REQUC_LOCAL unchanging_local + + +/**********************************************************************//** + If not present, may appear; but once becomes present, never goes absent +**************************************************************************/ +static enum req_unchanging_status + unchanging_present(enum req_unchanging_status def, + const struct req_context *context, + const struct requirement *req) +{ + if (TRI_YES != tri_req_present(context, NULL, req)) { + return REQUCH_NO; + } + return def; +} +#define REQUC_PRESENT unchanging_present + +/**********************************************************************//** + Equals ..._present(), but never changes in World range +**************************************************************************/ +static enum req_unchanging_status + unchanging_world(enum req_unchanging_status def, + const struct req_context *context, + const struct requirement *req) +{ + return + unchanging_present(req->range == REQ_RANGE_WORLD ? REQUCH_YES : def, + context, req); +} +#define REQUC_WORLD unchanging_world + +/**********************************************************************//** + Unchanging except if provided by an ally and not the player themself + Alliances may break, team members may be destroyed or reassigned +**************************************************************************/ +static enum req_unchanging_status + unchanging_noally(enum req_unchanging_status def, + const struct req_context *context, + const struct requirement *req) +{ + if (REQ_RANGE_ALLIANCE == req->range + || REQ_RANGE_TEAM == req->range) { + struct requirement preq; + + preq = *req; + preq.range = REQ_RANGE_PLAYER; + if (TRI_YES != tri_req_present(context, NULL, &preq)) { + return REQ_RANGE_TEAM == req->range ? REQUCH_ACT : REQUCH_NO; + } + } + return def; +} +#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 +**************************************************************************/ +static enum req_unchanging_status + unchanging_citytile(enum req_unchanging_status def, + const struct req_context *context, + const struct requirement *req) +{ + fc_assert_ret_val(VUT_CITYTILE == req->source.kind, REQUCH_NO); + if (CITYT_CENTER == 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 */ + return REQUCH_YES; + } + return def; +} +#define REQUC_CITYTILE unchanging_citytile + +/**********************************************************************//** + Special CityStatus case handler. Currently OwnedByOriginal only +**************************************************************************/ +static enum req_unchanging_status + unchanging_citystatus(enum req_unchanging_status def, + const struct req_context *context, + const struct requirement *req) +{ + fc_assert_ret_val(VUT_CITYSTATUS == req->source.kind, REQUCH_NO); + if (REQ_RANGE_CITY == req->range) { + return REQUCH_CTRL; + } + return def; +} +#define REQUC_CITYSTATUS unchanging_citystatus + +/**********************************************************************//** + Special Building case handler. + Sometimes building is just a constant parameter, and sometimes + it subjects to wonder building rules. Also, there is obsoletion... +**************************************************************************/ +static enum req_unchanging_status + unchanging_building(enum req_unchanging_status def, + const struct req_context *context, + const struct requirement *req) +{ + const struct impr_type *b = req->source.value.building; + + fc_assert_ret_val(VUT_IMPROVEMENT == req->source.kind, REQUCH_NO); + + if (REQ_RANGE_LOCAL == req->range) { + /* Likely, won't be questioned for an obsolete building */ + return REQUCH_YES; + } + + if (improvement_obsolete(context->player, b, context->city)) { + /* FIXME: sometimes can unobsolete, but considering it + * may sometimes put the function on endless recursion */ + return REQUCH_ACT; /* Mostly about techs */ + } + if (is_great_wonder(b)) { + if (great_wonder_is_destroyed(b) + || (!great_wonder_is_available(b) + && (req->range <= REQ_RANGE_CITY && TRI_YES + == tri_req_present(context, NULL, req)))) { + /* If the wonder stays somewhere, it may either remain there + * or be destroyed. If it is destroyed, it is nowhere. */ + return REQUCH_SCRIPTS; + } + } + return def; +} +#define REQUC_IMPR unchanging_building + +struct req_def { + const is_req_active_cb cb; + enum req_unchanging_status unchanging; + req_unchanging_cond_cb unchanging_cond; +}; + /**********************************************************************//** Parse requirement type (kind) and value strings into a universal structure. Passing in a NULL type is considered VUT_NONE (not an error). @@ -1345,6 +1509,39 @@ bool does_req_contradicts_reqs(const struct requirement *req, return FALSE; } +/**********************************************************************//** + Returns TRUE if tiles are in given requirements range with each other +**************************************************************************/ +static inline bool are_tiles_in_range(const struct tile *tile1, + const struct tile *tile2, + enum req_range range) +{ + switch (range) { + case REQ_RANGE_ADJACENT: + if (is_tiles_adjacent(tile1, tile2)) { + return TRUE; + } + if (same_pos(tile1, tile2)) { + return TRUE; + } + break; + case REQ_RANGE_CADJACENT: + return map_distance(tile1, tile2) <= 1; + case REQ_RANGE_CITY: + case REQ_RANGE_TRADEROUTE: + case REQ_RANGE_LOCAL: + case REQ_RANGE_CONTINENT: + case REQ_RANGE_PLAYER: + case REQ_RANGE_TEAM: + case REQ_RANGE_ALLIANCE: + case REQ_RANGE_WORLD: + case REQ_RANGE_COUNT: + /* Invalid */ + fc_assert(FALSE); + } + return FALSE; +} + /**********************************************************************//** Returns TRUE if players are in the same requirements range. **************************************************************************/ @@ -1375,12 +1572,6 @@ static inline bool players_in_same_range(const struct player *pplayer1, return FALSE; } -/* Function pointer for requirement-type-specific is_req_active handlers */ -typedef enum fc_tristate -(*is_req_active_cb)(const struct req_context *context, - const struct player *other_player, - const struct requirement *req); - #define IS_REQ_ACTIVE_VARIANT_ASSERT(_kind) \ { \ fc_assert_ret_val(req != NULL, TRI_MAYBE); \ @@ -3829,6 +4020,61 @@ is_serversetting_req_active(const struct req_context *context, req->source.value.ssetval)); } +/* Not const for potential ruleset-related adjustment */ +static struct req_def req_definitions[VUT_COUNT] = { + [VUT_NONE] = {is_none_req_active, REQUCH_YES}, + + /* alphabetical order of enum constant */ + [VUT_ACHIEVEMENT] = {is_achievement_req_active, REQUCH_YES, REQUC_PRESENT}, + [VUT_ACTION] = {is_action_req_active, REQUCH_YES}, + [VUT_ACTIVITY] = {is_activity_req_active, REQUCH_NO}, + [VUT_ADVANCE] = {is_tech_req_active, REQUCH_NO}, + [VUT_AGE] = {is_age_req_active, REQUCH_YES, REQUC_PRESENT}, + [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_DIPLREL] = {is_diplrel_req_active, REQUCH_NO}, + [VUT_DIPLREL_TILE] = {is_diplrel_tile_req_active, REQUCH_NO}, + [VUT_DIPLREL_TILE_O] = {is_diplrel_tile_o_req_active, REQUCH_NO}, + [VUT_DIPLREL_UNITANY] = {is_diplrel_unitany_req_active, REQUCH_NO}, + [VUT_DIPLREL_UNITANY_O] = {is_diplrel_unitany_o_req_active, REQUCH_NO}, + [VUT_EXTRA] = {is_extra_req_active, REQUCH_NO, REQUC_LOCAL}, + [VUT_EXTRAFLAG] = {is_extraflag_req_active, REQUCH_NO, REQUC_LOCAL}, + [VUT_GOOD] = {is_good_req_active, REQUCH_NO}, + [VUT_GOVERNMENT] = {is_gov_req_active, REQUCH_NO}, + [VUT_IMPROVEMENT] = {is_building_req_active, REQUCH_NO, REQUC_IMPR}, + [VUT_IMPR_GENUS] = {is_buildinggenus_req_active, REQUCH_YES}, + [VUT_MAXTILEUNITS] = {is_maxunitsontile_req_active, REQUCH_NO}, + [VUT_MINCALFRAG] = {is_mincalfrag_req_active, REQUCH_NO}, + [VUT_MINCULTURE] = {is_minculture_req_active, REQUCH_NO}, + [VUT_MINFOREIGNPCT] = {is_minforeignpct_req_active, REQUCH_NO}, + [VUT_MINHP] = {is_minhitpoints_req_active, REQUCH_NO}, + [VUT_MINMOVES] = {is_minmovefrags_req_active, REQUCH_NO}, + [VUT_MINSIZE] = {is_minsize_req_active, REQUCH_NO}, + [VUT_MINTECHS] = {is_mintechs_req_active, REQUCH_ACT, REQUC_WORLD}, + [VUT_MINVETERAN] = {is_minveteran_req_active, REQUCH_SCRIPTS, REQUC_PRESENT}, + [VUT_MINYEAR] = {is_minyear_req_active, REQUCH_HACK, REQUC_PRESENT}, + [VUT_NATION] = {is_nation_req_active, REQUCH_HACK, REQUC_NALLY}, + [VUT_NATIONALITY] = {is_nationality_req_active, REQUCH_NO}, + [VUT_NATIONGROUP] = {is_nationgroup_req_active, REQUCH_HACK, REQUC_NALLY}, + [VUT_OTYPE] = {is_outputtype_req_active, REQUCH_YES}, + [VUT_ROADFLAG] = {is_roadflag_req_active, REQUCH_NO, REQUC_LOCAL}, + [VUT_SERVERSETTING] = {is_serversetting_req_active, REQUCH_HACK}, + [VUT_SPECIALIST] = {is_specialist_req_active, REQUCH_YES}, + [VUT_STYLE] = {is_style_req_active, REQUCH_HACK}, + [VUT_TECHFLAG] = {is_techflag_req_active, REQUCH_NO}, + [VUT_TERRAIN] = {is_terrain_req_active, REQUCH_NO}, + [VUT_TERRAINALTER] = {is_terrainalter_req_active, REQUCH_NO}, + [VUT_TERRAINCLASS] = {is_terrainclass_req_active, REQUCH_NO}, + [VUT_TERRFLAG] = {is_terrainflag_req_active, REQUCH_NO}, + [VUT_TOPO] = {is_topology_req_active, REQUCH_YES}, + [VUT_UCFLAG] = {is_unitclassflag_req_active, REQUCH_YES}, + [VUT_UCLASS] = {is_unitclass_req_active, REQUCH_YES}, + [VUT_UNITSTATE] = {is_unitstate_req_active, REQUCH_NO}, + [VUT_UTFLAG] = {is_unitflag_req_active, REQUCH_YES}, + [VUT_UTYPE] = {is_unittype_req_active, REQUCH_YES} +}; + /**********************************************************************//** Checks the requirement to see if it is active on the given target. @@ -3846,73 +4092,7 @@ bool is_req_active(const struct req_context *context, const struct requirement *req, const enum req_problem_type prob_type) { - static const is_req_active_cb req_active_callbacks[VUT_COUNT] = { - [VUT_NONE] = is_none_req_active, - - /* alphabetical order of enum constant */ - [VUT_ACHIEVEMENT] = is_achievement_req_active, - [VUT_ACTION] = is_action_req_active, - [VUT_ACTIVITY] = is_activity_req_active, - [VUT_ADVANCE] = is_tech_req_active, - [VUT_AGE] = is_age_req_active, - [VUT_AI_LEVEL] = is_ai_req_active, - [VUT_CITYSTATUS] = is_citystatus_req_active, - [VUT_CITYTILE] = is_citytile_req_active, - [VUT_DIPLREL] = is_diplrel_req_active, - [VUT_DIPLREL_TILE] = is_diplrel_tile_req_active, - [VUT_DIPLREL_TILE_O] = is_diplrel_tile_o_req_active, - [VUT_DIPLREL_UNITANY] = is_diplrel_unitany_req_active, - [VUT_DIPLREL_UNITANY_O] = is_diplrel_unitany_o_req_active, - [VUT_EXTRA] = is_extra_req_active, - [VUT_EXTRAFLAG] = is_extraflag_req_active, - [VUT_GOOD] = is_good_req_active, - [VUT_GOVERNMENT] = is_gov_req_active, - [VUT_IMPROVEMENT] = is_building_req_active, - [VUT_IMPR_GENUS] = is_buildinggenus_req_active, - [VUT_MAXTILEUNITS] = is_maxunitsontile_req_active, - [VUT_MINCALFRAG] = is_mincalfrag_req_active, - [VUT_MINCULTURE] = is_minculture_req_active, - [VUT_MINFOREIGNPCT] = is_minforeignpct_req_active, - [VUT_MINHP] = is_minhitpoints_req_active, - [VUT_MINMOVES] = is_minmovefrags_req_active, - [VUT_MINSIZE] = is_minsize_req_active, - [VUT_MINTECHS] = is_mintechs_req_active, - [VUT_MINVETERAN] = is_minveteran_req_active, - [VUT_MINYEAR] = is_minyear_req_active, - [VUT_NATION] = is_nation_req_active, - [VUT_NATIONALITY] = is_nationality_req_active, - [VUT_NATIONGROUP] = is_nationgroup_req_active, - [VUT_OTYPE] = is_outputtype_req_active, - [VUT_ROADFLAG] = is_roadflag_req_active, - [VUT_SERVERSETTING] = is_serversetting_req_active, - [VUT_SPECIALIST] = is_specialist_req_active, - [VUT_STYLE] = is_style_req_active, - [VUT_TECHFLAG] = is_techflag_req_active, - [VUT_TERRAIN] = is_terrain_req_active, - [VUT_TERRAINALTER] = is_terrainalter_req_active, - [VUT_TERRAINCLASS] = is_terrainclass_req_active, - [VUT_TERRFLAG] = is_terrainflag_req_active, - [VUT_TOPO] = is_topology_req_active, - [VUT_UCFLAG] = is_unitclassflag_req_active, - [VUT_UCLASS] = is_unitclass_req_active, - [VUT_UNITSTATE] = is_unitstate_req_active, - [VUT_UTFLAG] = is_unitflag_req_active, - [VUT_UTYPE] = is_unittype_req_active, - }; - enum fc_tristate eval; - - if (context == NULL) { - context = req_context_empty(); - } - - if (req->source.kind >= VUT_COUNT) { - log_error("is_req_active(): invalid source kind %d.", req->source.kind); - return FALSE; - } - - fc_assert_ret_val(req_active_callbacks[req->source.kind] != NULL, FALSE); - - eval = req_active_callbacks[req->source.kind](context, other_player, req); + enum fc_tristate eval = tri_req_present(context, other_player, req); if (eval == TRI_MAYBE) { if (prob_type == RPT_POSSIBLE) { @@ -3928,6 +4108,34 @@ bool is_req_active(const struct req_context *context, } } +/**********************************************************************//** + Applies the standard evaluation of req in context, ignoring req->present. + + context may be NULL. This is equivalent to passing an empty context. + + Fields of context that are NULL are considered unspecified + and will produce TRI_MAYBE if req needs them to evaluate. +**************************************************************************/ +static +enum fc_tristate tri_req_present(const struct req_context *context, + const struct player *other_player, + const struct requirement *req) +{ + if (context == NULL) { + context = req_context_empty(); + } + + if (req->source.kind >= VUT_COUNT) { + log_error("tri_req_present(): invalid source kind %d.", + req->source.kind); + return TRI_NO; + } + + fc_assert_ret_val(req_definitions[req->source.kind].cb != NULL, TRI_NO); + + return req_definitions[req->source.kind].cb(context, other_player, req); +} + /**********************************************************************//** Checks the requirement(s) to see if they are active on the given target. @@ -3956,82 +4164,85 @@ bool are_reqs_active(const struct req_context *context, } /**********************************************************************//** - Return TRUE if this is an "unchanging" requirement. This means that - if a target can't meet the requirement now, it probably won't ever be able - to do so later. This can be used to do requirement filtering when checking - if a target may "eventually" become available. + Gives a suggestion may req ever evaluate to another value with given + context. (The other player is not supplied since it has no value + for changeability of any requirement for now.) - Note this isn't absolute. Returning TRUE here just means that the - requirement probably can't be met. In some cases (particularly terrains) - it may be wrong. + Note this isn't absolute. Result other than REQUCH_NO here just means ++ that the requirement _probably_ can't change its value afterwards. ***************************************************************************/ -bool is_req_unchanging(const struct requirement *req) +enum req_unchanging_status + is_req_unchanging(const struct req_context *context, + const struct requirement *req) { - switch (req->source.kind) { - case VUT_NONE: - case VUT_ACTION: - 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_CITYSTATUS: /* We don't *want* owner of our city to change */ - case VUT_STYLE: - case VUT_TOPO: - case VUT_SERVERSETTING: - return TRUE; - case VUT_NATION: - case VUT_NATIONGROUP: - return (req->range != REQ_RANGE_ALLIANCE); - case VUT_ADVANCE: - case VUT_TECHFLAG: - case VUT_GOVERNMENT: - case VUT_ACHIEVEMENT: - case VUT_IMPROVEMENT: - case VUT_IMPR_GENUS: - case VUT_MINSIZE: - case VUT_MINCULTURE: - case VUT_MINFOREIGNPCT: - case VUT_MINTECHS: - case VUT_NATIONALITY: - case VUT_DIPLREL: - case VUT_DIPLREL_TILE: - case VUT_DIPLREL_TILE_O: - case VUT_DIPLREL_UNITANY: - case VUT_DIPLREL_UNITANY_O: - case VUT_MAXTILEUNITS: - case VUT_UTYPE: /* Not sure about this one */ - case VUT_UTFLAG: /* Not sure about this one */ - case VUT_UCLASS: /* Not sure about this one */ - case VUT_UCFLAG: /* Not sure about this one */ - case VUT_MINVETERAN: - case VUT_UNITSTATE: - case VUT_ACTIVITY: - case VUT_MINMOVES: - case VUT_MINHP: - case VUT_AGE: - case VUT_ROADFLAG: - case VUT_EXTRAFLAG: - case VUT_MINCALFRAG: /* cyclically available */ - return FALSE; - case VUT_TERRAIN: - case VUT_EXTRA: - case VUT_GOOD: - case VUT_TERRAINCLASS: - case VUT_TERRFLAG: - case VUT_TERRAINALTER: - /* Terrains, specials and bases aren't really unchanging; in fact they're - * practically guaranteed to change. We return TRUE here for historical - * reasons and so that the AI doesn't get confused (since the AI - * doesn't know how to meet special and terrain requirements). */ - return TRUE; - case VUT_MINYEAR: - /* Once year is reached, it does not change again */ - return req->source.value.minyear > game.info.year; - case VUT_COUNT: - break; + enum req_unchanging_status s; + + fc_assert_ret_val(req, REQUCH_NO); + fc_assert_ret_val_msg(universals_n_is_valid(req->source.kind), REQUCH_NO, + "Invalid source kind %d.", req->source.kind); + s = req_definitions[req->source.kind].unchanging; + + if (req->survives) { + /* Special case for surviving requirements */ + /* Buildings may obsolete even here */ + if (VUT_IMPROVEMENT == req->source.kind) { + const struct impr_type *b = req->source.value.building; + + if (can_improvement_go_obsolete(b)) { + if (improvement_obsolete(context->player, b, context->city)) { + /* FIXME: sometimes can unobsolete, but considering it + * may sometimes put the function on endless recursion */ + return REQUCH_ACT; /* Mostly about techs */ + } else { + /* NOTE: may obsoletion reqs be unchanging? Hardly but why not. */ + return REQUCH_NO; + } + } + } + s = unchanging_present(s, context, req); + if (s != REQUCH_NO) { + return unchanging_noally(s, context, req); + } + } else { + req_unchanging_cond_cb cond + = req_definitions[req->source.kind].unchanging_cond; + + if (cond) { + return cond(s, context, req); + } } - fc_assert_msg(FALSE, "Invalid source kind %d.", req->source.kind); - return TRUE; + + return s; +} + +/**********************************************************************//** + Returns whether this requirement is unfulfilled and probably won't be ever +***************************************************************************/ +enum req_unchanging_status + is_req_preventing(const struct req_context *context, + const struct player *other_player, + const struct requirement *req, + enum req_problem_type prob_type) +{ + enum req_unchanging_status u = is_req_unchanging(context, req); + + if (REQUCH_NO != u) { + /* presence is precalculated */ + bool auto_present + = (req->survives + && !(VUT_IMPROVEMENT == req->source.kind + && can_improvement_go_obsolete(req->source.value.building))) + || REQUC_PRESENT == req_definitions[req->source.kind].unchanging_cond + || REQUC_WORLD == req_definitions[req->source.kind].unchanging_cond; + + if (auto_present ? req->present + : is_req_active(context, other_player, req, prob_type)) { + /* Unchanging but does not block */ + return REQUCH_NO; + } + } + + return u; } /**********************************************************************//** diff --git a/common/requirements.h b/common/requirements.h index 84e2f93aa7..cdd834a292 100644 --- a/common/requirements.h +++ b/common/requirements.h @@ -107,6 +107,25 @@ struct req_context { const struct action *action; }; +enum req_unchanging_status { + REQUCH_NO = 0, /* Changes regulary */ + REQUCH_CTRL, /* Can't be changed by game means as long as target player + * is in control of target city or unit */ + REQUCH_ACT, /* Can't be easily changed by expected player's activity + * (without destroying teammates, inducing global warming...) */ + REQUCH_SCRIPTS, /* May be changed by script callbacks */ + REQUCH_HACK, /* May be changed by server commands/editor */ + REQUCH_YES /* Really never changes unless savegame is edited */ +}; + +/* A callback that may transform kind-specific default unchanging status + * to another one (usually higher but not always) + * Passing other_player is just not needed for it in any known cases */ +typedef enum req_unchanging_status + (*req_unchanging_cond_cb)(enum req_unchanging_status def, + const struct req_context *context, + const struct requirement *req); + /* req_context-related functions */ const struct req_context *req_context_empty(void); @@ -142,7 +161,14 @@ bool are_reqs_active(const struct req_context *context, const struct requirement_vector *reqs, const enum req_problem_type prob_type); -bool is_req_unchanging(const struct requirement *req); +enum req_unchanging_status + is_req_unchanging(const struct req_context *context, + const struct requirement *req); +enum req_unchanging_status + is_req_preventing(const struct req_context *context, + const struct player *other_player, + const struct requirement *req, + enum req_problem_type prob_type); bool is_req_in_vec(const struct requirement *req, const struct requirement_vector *vec); diff --git a/common/research.c b/common/research.c index f52adec14f..3af763bbe4 100644 --- a/common/research.c +++ b/common/research.c @@ -304,8 +304,7 @@ static bool reqs_may_activate(const struct req_context *context, const enum req_problem_type prob_type) { requirement_vector_iterate(reqs, preq) { - if (is_req_unchanging(preq) - && !is_req_active(context, other_player, preq, prob_type)) { + if (is_req_preventing(context, other_player, preq, prob_type)) { return FALSE; } } requirement_vector_iterate_end; diff --git a/server/rssanity.c b/server/rssanity.c index 99e94f1228..48e5a93a1e 100644 --- a/server/rssanity.c +++ b/server/rssanity.c @@ -897,7 +897,10 @@ bool sanity_check_ruleset_data(struct rscompat_info *compat) " and req2 like before.", advance_rule_name(padvance)); ok = FALSE; - } else if (!is_req_unchanging(preq)) { + } else if (is_req_unchanging(NULL, preq) < REQUCH_HACK + /* If we get an obsolete improvement before the game, + * almost surely it is going to become not obsolete later. + * This check must catch it. */) { struct astring astr; /* Only support unchanging requirements until the reachability code -- 2.34.1