From d1da16a02195a1e68b8f2f10d137495e407f035f Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Wed, 9 Nov 2022 17:28:43 +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 it seems theoretically possible. AI gets some source kings as unchanging by legacy. See OSDN#46029 Signed-off-by: Ihnatus --- ai/default/aicity.c | 85 ++++++++++++++++++++++++++++++++++ ai/default/aicity.h | 6 +++ common/city.c | 3 +- common/improvement.c | 9 ++-- common/improvement.h | 4 +- common/requirements.c | 104 ++++++++++++++++++++++++++++++++---------- common/requirements.h | 22 ++++++++- common/research.c | 3 +- server/rssanity.c | 2 +- 9 files changed, 201 insertions(+), 37 deletions(-) diff --git a/ai/default/aicity.c b/ai/default/aicity.c index b22b0a07e4..d7582171a1 100644 --- a/ai/default/aicity.c +++ b/ai/default/aicity.c @@ -1828,6 +1828,91 @@ static bool should_force_recalc(struct city *pcity) && !can_city_build_improvement_later(pcity, pcity->production.value.building)); } +/**********************************************************************//** + 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 player *target_player, + const struct city *target_city, + const struct tile *target_tile, + 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(target_player, NULL, target_city, + NULL, target_tile, NULL, NULL, + NULL, NULL, NULL, req, RPT_POSSIBLE); + default: + return is_req_preventing(target_player, NULL, target_city, + NULL, target_tile, NULL, NULL, + NULL, NULL, NULL, req, RPT_POSSIBLE); + } +} + +/**********************************************************************//** + 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 player *pplayer = city_owner(pcity); + const struct tile *ptile = 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(pplayer, 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(pplayer, pcity, ptile, 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) +{ + 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(p, NULL, NULL, 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; +} + /************************************************************************** Initialize building advisor. Calculates data of all players, not only of those controlled by current ai type. diff --git a/ai/default/aicity.h b/ai/default/aicity.h index 5d64811ea5..3e29ea70cf 100644 --- a/ai/default/aicity.h +++ b/ai/default/aicity.h @@ -94,6 +94,12 @@ void dont_want_tech_obsoleting_impr(struct ai_type *ait, const struct impr_type *pimprove, adv_want building_want); +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); + void dai_build_adv_init(struct ai_type *ait, struct player *pplayer); void dai_build_adv_adjust(struct ai_type *ait, struct player *pplayer, struct city *wonder_city); diff --git a/common/city.c b/common/city.c index 8dffe0b309..0a0474da91 100644 --- a/common/city.c +++ b/common/city.c @@ -856,8 +856,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_owner(pcity), NULL, pcity, NULL, + if (is_req_preventing(city_owner(pcity), NULL, pcity, NULL, pcity->tile, NULL, NULL, NULL, NULL, NULL, preq, RPT_POSSIBLE)) { return FALSE; diff --git a/common/improvement.c b/common/improvement.c index bad914fc7d..7691933787 100644 --- a/common/improvement.c +++ b/common/improvement.c @@ -206,7 +206,7 @@ struct impr_type *improvement_by_number(const Impr_type_id id) tech_req to A_LAST; [was not in current 2007-07-27] - it is a space part, and the spacerace is not enabled. **************************************************************************/ -struct impr_type *valid_improvement(struct impr_type *pimprove) +const struct impr_type *valid_improvement(const struct impr_type *pimprove) { if (NULL == pimprove) { return NULL; @@ -229,7 +229,7 @@ struct impr_type *valid_improvement(struct impr_type *pimprove) In addition to valid_improvement(), tests for id is out of range. **************************************************************************/ -struct impr_type *valid_improvement_by_number(const Impr_type_id id) +const struct impr_type *valid_improvement_by_number(const Impr_type_id id) { return valid_improvement(improvement_by_number(id)); } @@ -711,9 +711,8 @@ 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(p, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, preq, RPT_POSSIBLE)) { + && is_req_preventing(p, NULL, NULL, NULL, NULL, NULL, NULL, + NULL,NULL, NULL, preq, RPT_POSSIBLE)) { return FALSE; } } requirement_vector_iterate_end; diff --git a/common/improvement.h b/common/improvement.h index 6142d39d5b..cc24c308c6 100644 --- a/common/improvement.h +++ b/common/improvement.h @@ -99,8 +99,8 @@ Impr_type_id improvement_number(const struct impr_type *pimprove); struct impr_type *improvement_by_number(const Impr_type_id id); -struct impr_type *valid_improvement(struct impr_type *pimprove); -struct impr_type *valid_improvement_by_number(const Impr_type_id id); +const struct impr_type *valid_improvement(const struct impr_type *pimprove); +const struct impr_type *valid_improvement_by_number(const Impr_type_id id); struct impr_type *improvement_by_rule_name(const char *name); struct impr_type *improvement_by_translated_name(const char *name); diff --git a/common/requirements.c b/common/requirements.c index be4f0a45b1..19411b368e 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -3166,47 +3166,76 @@ bool are_reqs_active(const struct player *target_player, requirement probably can't be met. In some cases (particularly terrains) it may be wrong. *****************************************************************************/ -bool is_req_unchanging(const struct requirement *req) +enum req_unchanging_status is_req_unchanging(const struct requirement *req) { + if (req->survives) { + /* Teammate may be killed but we think about *our* actions */ + /* Player's own surviving effects won't change with alliances + * but let's not overcomplicate things */ + return (req->range == REQ_RANGE_ALLIANCE ? REQUCH_NO : REQUCH_PRESENT); + } switch (req->source.kind) { + /* Ruleset persistent object, game constant, server setting... */ case VUT_NONE: + case VUT_UTYPE: + case VUT_UTFLAG: + case VUT_UCLASS: + case VUT_UCFLAG: case VUT_ACTION: case VUT_OTYPE: - case VUT_SPECIALIST: /* Only so long as it's at local range only */ + case VUT_SPECIALIST: case VUT_AI_LEVEL: - case VUT_CITYTILE: case VUT_STYLE: case VUT_TOPO: case VUT_SERVERSETTING: - return TRUE; + case VUT_IMPR_GENUS: + case VUT_CITYTILE: /* As long as we query for city center */ + return REQUCH_ALWAYS; + /* Special cases */ case VUT_NATION: case VUT_NATIONGROUP: - return (req->range != REQ_RANGE_ALLIANCE); + /* Teammate may be killed but we think about *our* actions */ + return (req->range == REQ_RANGE_ALLIANCE ? REQUCH_NO : REQUCH_ALWAYS); + case VUT_IMPROVEMENT: + { + const struct impr_type *b = req->source.value.building; + + if (REQ_RANGE_LOCAL == req->range) { + /* Persistent */ + return REQUCH_ALWAYS; + } + if (is_great_wonder(b)){ + if (great_wonder_is_destroyed(b) + || (!great_wonder_is_available(b) + && req->range <= REQ_RANGE_CITY)) { + /* The wonder either is not here and never will be + * or is here and won't go away from the city */ + return REQUCH_ALWAYS; + } + } + return REQUCH_NO; + } + case VUT_MINTECHS: + if (REQ_RANGE_WORLD == req->range) { + return REQUCH_PRESENT; + } + fc__fallthrough; case VUT_ADVANCE: case VUT_TECHFLAG: + /* Hardly we are going to unlearn a tech but left changing */ + /* Easily changing ones */ case VUT_GOVERNMENT: - case VUT_ACHIEVEMENT: - case VUT_IMPROVEMENT: - case VUT_IMPR_GENUS: case VUT_MINSIZE: case VUT_MINCULTURE: - case VUT_MINTECHS: case VUT_NATIONALITY: case VUT_DIPLREL: 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_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: @@ -3214,19 +3243,46 @@ bool is_req_unchanging(const struct requirement *req) case VUT_TERRFLAG: case VUT_TERRAINALTER: case VUT_BASEFLAG: - /* 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; + return REQUCH_NO; + case VUT_MINVETERAN: + case VUT_AGE: + case VUT_ACHIEVEMENT: case VUT_MINYEAR: - /* Once year is reached, it does not change again */ - return req->source.value.minyear > game.info.year32; + return REQUCH_PRESENT; case VUT_COUNT: break; } fc_assert_msg(FALSE, "Invalid source kind %d.", req->source.kind); - return TRUE; + return REQUCH_ALWAYS; +} + +/**************************************************************************** + Returns whether this requirement is unfulfilled and probably won't be ever +****************************************************************************/ +bool is_req_preventing(const struct player *target_player, + const struct player *other_player, + const struct city *target_city, + const struct impr_type *target_building, + const struct tile *target_tile, + const struct unit *target_unit, + const struct unit_type *target_unittype, + const struct output_type *target_output, + const struct specialist *target_specialist, + const struct action *target_action, + const struct requirement *req, + enum req_problem_type prob_type) +{ + enum req_unchanging_status uc = is_req_unchanging(req); + + if ((uc == REQUCH_PRESENT ? !req->present : uc != REQUCH_NO) + && !is_req_active(target_player, other_player, target_city, + target_building, target_tile, + target_unit, target_unittype, + target_output, target_specialist, + target_action, req, prob_type)) { + return TRUE; + } + return FALSE; } /************************************************************************* diff --git a/common/requirements.h b/common/requirements.h index ec5b45c41b..988fc89d1f 100644 --- a/common/requirements.h +++ b/common/requirements.h @@ -82,6 +82,14 @@ struct requirement { bool quiet; /* do not list this in helptext */ }; +/* Specifier for how a requirement might probably + * change in the future */ +enum req_unchanging_status { + REQUCH_NO = 0, /* Changes freely */ + REQUCH_PRESENT, /* Changes forth but not back */ + REQUCH_ALWAYS /* Can't change any way */ +}; + #define SPECVEC_TAG requirement #define SPECVEC_TYPE struct requirement #include "specvec.h" @@ -139,7 +147,19 @@ bool are_reqs_active(const struct player *target_player, 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 requirement *req); +bool is_req_preventing(const struct player *target_player, + const struct player *other_player, + const struct city *target_city, + const struct impr_type *target_building, + const struct tile *target_tile, + const struct unit *target_unit, + const struct unit_type *target_unittype, + const struct output_type *target_output, + const struct specialist *target_specialist, + const struct action *target_action, + 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 ac06db033c..ed80b0845b 100644 --- a/common/research.c +++ b/common/research.c @@ -312,8 +312,7 @@ static bool reqs_may_activate(const struct player *target_player, const enum req_problem_type prob_type) { requirement_vector_iterate(reqs, preq) { - if (is_req_unchanging(preq) - && !is_req_active(target_player, other_player, target_city, + if (is_req_preventing(target_player, other_player, target_city, target_building, target_tile, target_unit, target_unittype, target_output, target_specialist, target_action, diff --git a/server/rssanity.c b/server/rssanity.c index ef2d794f07..3bdd06dac0 100644 --- a/server/rssanity.c +++ b/server/rssanity.c @@ -820,7 +820,7 @@ 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(preq) != REQUCH_ALWAYS) { struct astring astr; /* Only support unchanging requirements until the reachability code -- 2.34.1