From 0b77bd674eda8120fdaae4456df5d42d7e66c7a1 Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Thu, 10 Nov 2022 15:18:51 +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/aicity.c | 85 ++++++++++++++++++++++++++++ ai/default/aicity.h | 6 ++ common/city.c | 3 +- common/improvement.c | 9 ++- common/improvement.h | 4 +- common/requirements.c | 125 ++++++++++++++++++++++++++++++++++-------- common/requirements.h | 22 +++++++- common/research.c | 3 +- server/rssanity.c | 7 ++- 9 files changed, 227 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..2553148d80 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -3166,47 +3166,97 @@ 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) { + /* Buildings may obsolete even here */ + if (VUT_IMPROVEMENT == req->source.kind) { + const struct impr_type *b = req->source.value.building; + + /* All we can do without looking at the context or recursing + * in-depth is testing if the improvement is already + * globally obsolete. If so, we suggest that it won't + * unobsolete. If not, we suggest it can be built or obsolete. */ + if (can_improvement_go_obsolete(b)) { + if (improvement_obsolete(NULL, b, NULL)) { + return REQUCH_ALWAYS; + } else { + return REQUCH_NO; + } + } + } + + /* 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; + } + /* We suggest we'll never see a globally obsolete building again */ + if (improvement_obsolete(NULL, b, NULL)) { + 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 +3264,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..c0fe8600d5 100644 --- a/server/rssanity.c +++ b/server/rssanity.c @@ -820,7 +820,12 @@ 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 + /* If we get an obsolete improvement before the game, + * almost surely it is going to become not obsolete later */ + || (VUT_IMPROVEMENT == preq->source.kind + && can_improvement_go_obsolete + (preq->source.value.building))) { struct astring astr; /* Only support unchanging requirements until the reachability code -- 2.34.1