From 935161c966eae14c91b6faad3358a89e47f27b91 Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Tue, 13 Dec 2022 23:18:45 +0300 Subject: [PATCH] AI: Improve unit danger calculation Look at "Attack_Bonus" effect. Look at action enablers that may fire. If a unit may occupy city or bring occupiers, never consider it completely harmless. See OSDN#46046 Signed-off-by: Ihnatus --- ai/default/daimilitary.c | 214 +++++++++++++++++++++++++++++++++++++-- common/requirements.c | 117 +++++++++++++++++++++ common/requirements.h | 5 + 3 files changed, 330 insertions(+), 6 deletions(-) diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c index 19a536464a..1b64b2ac1a 100644 --- a/ai/default/daimilitary.c +++ b/ai/default/daimilitary.c @@ -345,6 +345,162 @@ static int assess_defense_igwall(struct ai_type *ait, struct city *pcity) return assess_defense_backend(ait, pcity, TRUE); } +/**********************************************************************//** + While assuming an attack on a target in few turns, tries to guess + if a requirement req will be fulfilled for this target + n_data for a number of turns after which the strike is awaited + (0 means current turn, assumes a strike during 5 turns) +**************************************************************************/ +static enum fc_tristate +tactical_req_cb(const struct req_context *context, + const struct player *other_player, + const struct requirement *req, + void *data, int n_data) +{ + switch (req->source.kind) { + case VUT_IMPROVEMENT: + { + const struct impr_type *b = req->source.value.building; + + /* FIXME: in actor_reqs, may allow attack _from_ a city with... */ + if (req->survives || NULL == context->city || is_great_wonder(b) + || !city_has_building(context->city, b) || b->sabotage <= 0) { + return tri_req_active(context, other_player, req); + } + /* Else may be sabotaged */ + } + fc__fallthrough; + case VUT_UNITSTATE: + case VUT_ACTIVITY: + case VUT_MINSIZE: + case VUT_MAXTILEUNITS: + case VUT_MINHP: + case VUT_MINMOVES: + case VUT_COUNTER: + /* Can be changed back or forth quickly */ + return TRI_MAYBE; + case VUT_MINVETERAN: + /* Can be changed forth but not back */ + return is_req_preventing(context, other_player, req, RPT_POSSIBLE) + > REQUCH_CTRL ? TRI_NO : TRI_MAYBE; + case VUT_MINFOREIGNPCT: + case VUT_NATIONALITY: + /* Can be changed back but hardly forth (foreign citizens reduced first) */ + switch (tri_req_active(context, other_player, req)) { + case TRI_NO: + return req->present ? TRI_NO : TRI_MAYBE; + case TRI_YES: + return req->present ? TRI_MAYBE : TRI_YES; + default: + return TRI_MAYBE; + } + case VUT_DIPLREL: + case VUT_DIPLREL_TILE: + case VUT_DIPLREL_TILE_O: + case VUT_DIPLREL_UNITANY: + case VUT_DIPLREL_UNITANY_O: + /* If the attack happens, there is a diplrel that allows it */ + return TRI_YES; + case VUT_AGE: + case VUT_MINCALFRAG: + case VUT_MINYEAR: + /* If it is not near, won't change */ + return tri_req_active_turns(n_data, 5 /* WAG */, + context, other_player, req); + case VUT_CITYSTATUS: + if (CITYS_OWNED_BY_ORIGINAL != req->source.value.citystatus) { + return TRI_MAYBE; + } + fc__fallthrough; + case VUT_UTYPE: + case VUT_UTFLAG: + case VUT_UCLASS: + case VUT_UCFLAG: + /* FIXME: support converting siege machines (needs hard reqs checked) */ + case VUT_ACTION: + case VUT_OTYPE: + case VUT_SPECIALIST: + case VUT_EXTRAFLAG: + case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: + case VUT_AI_LEVEL: + case VUT_CITYTILE: + case VUT_STYLE: + case VUT_TOPO: + case VUT_SERVERSETTING: + case VUT_NATION: + case VUT_NATIONGROUP: + case VUT_ADVANCE: + case VUT_TECHFLAG: + case VUT_GOVERNMENT: + case VUT_ACHIEVEMENT: + case VUT_IMPR_GENUS: + case VUT_MINCULTURE: + case VUT_MINTECHS: + case VUT_ORIGINAL_OWNER: + case VUT_ROADFLAG: + case VUT_TERRAIN: + case VUT_EXTRA: + case VUT_GOOD: + case VUT_TERRAINCLASS: + case VUT_TERRFLAG: + case VUT_TERRAINALTER: + case VUT_NONE: + return tri_req_active(context, other_player, req); + case VUT_COUNT: + /* Not implemented. */ + break; + } + fc_assert_ret_val(FALSE, TRI_NO); +} + +/**********************************************************************//** + See if there is an enabler for particular action to be targeted at + pcity's tile after turns (for 5 turns more). + Note that hard reqs are not tested except for utype + and that the actor player is ignored; also, it's not tested can utype + attack the city's particular terrain. +**************************************************************************/ +static bool +action_may_happen_unit_on_city(const action_id wanted_action, + const struct unit *actor, + const struct city *pcity, + int turns) +{ + const struct player *target_player = city_owner(pcity), + *actor_player = unit_owner(actor); + const struct unit_type *utype = unit_type_get(actor); + const struct req_context target_ctx = { + .player = target_player, + .city = pcity, + .tile = city_tile(pcity) + }, actor_ctx = { + .player = actor_player, + .unit = actor, + .unittype = utype + }; + + if (!utype_can_do_action(utype, wanted_action)) { + return FALSE; + } + action_enabler_list_iterate(action_enablers_for_action(wanted_action), + enabler) { + /* We assume that we could build or move units into the city + * that are not present there yet */ + if (TRI_NO != tri_reqs_cb_active(&actor_ctx, target_player, + &enabler->actor_reqs, NULL, + tactical_req_cb, NULL, turns) + && + TRI_NO != tri_reqs_cb_active(&target_ctx, actor_player, + &enabler->target_reqs, NULL, + tactical_req_cb, NULL, turns)) { + return TRUE; + } + } action_enabler_list_iterate_end; + + return FALSE; +} + /**********************************************************************//** How dangerous and far a unit is for a city? **************************************************************************/ @@ -356,9 +512,11 @@ static unsigned int assess_danger_unit(const struct city *pcity, struct pf_position pos; const struct unit_type *punittype = unit_type_get(punit); const struct tile *ptile = city_tile(pcity); + const struct player *uowner = unit_owner(punit); const struct unit *ferry; unsigned int danger; - int mod; + int amod = -99, dmod; + bool attack_danger = FALSE; *move_time = PF_IMPOSSIBLE_MC; @@ -398,10 +556,52 @@ static unsigned int assess_danger_unit(const struct city *pcity, return 0; } + /* Find the worst attack action to expect */ + action_by_result_iterate(paction, id, ACTRES_ATTACK) { + /* Is it possible that punit will do action id to the city? */ + /* FIXME: some unit parameters (notably, veterancy) may meddle in */ + int b; + + if (action_may_happen_unit_on_city(id, punit, pcity, *move_time)) { + attack_danger = TRUE; + } else { + continue; + } + b = get_unittype_bonus(uowner, ptile, punittype, paction, EFT_ATTACK_BONUS); + if (b > amod) { + amod = b; + } + } action_by_result_iterate_end; + + /* FIXME: it's a dummy support for anti-bombard defense just to do something against + * approaching bombarders. Some better logic is needed, see OSDN#41778 */ + if (!attack_danger) { + action_by_result_iterate(paction, id, ACTRES_BOMBARD) { + /* FIXME: some unit parameters (notably, veterancy) may meddle in */ + int b; + + if (action_may_happen_unit_on_city(id, punit, pcity, *move_time)) { + attack_danger = TRUE; + } else { + continue; + } + b = get_unittype_bonus(uowner, ptile, punittype, paction, EFT_ATTACK_BONUS); + if (b > amod) { + amod = b; + } + } action_by_result_iterate_end; + /* Here something should be done cuz the modifier affects + * more than one unit but not their full hp, but is not done yet...*/ + } + if (!attack_danger) { + /* If the unit is dangerous, it's not about its combat strength */ + return 0; + } + danger = adv_unit_att_rating(punit); - mod = 100 + get_unittype_bonus(city_owner(pcity), ptile, - punittype, NULL, EFT_DEFEND_BONUS); - return danger * 100 / MAX(mod, 1); + dmod = 100 + get_unittype_bonus(city_owner(pcity), ptile, + punittype, NULL, EFT_DEFEND_BONUS); + return danger * (amod + 100) / MAX(dmod, 1); } /**********************************************************************//** @@ -639,8 +839,10 @@ static unsigned int assess_danger(struct ai_type *ait, struct city *pcity, continue; } - if ((0 < vulnerability && unit_can_take_over(punit)) - || utai->carries_occupiers) { + if (unit_can_take_over(punit) || utai->carries_occupiers) { + /* Even if there is no attack strength, + * we need ANY protector for the city */ + vulnerability = MAX(vulnerability, 1); if (3 >= move_time) { urgency++; if (1 >= move_time) { diff --git a/common/requirements.c b/common/requirements.c index 5b2e91ef5f..c81f1c5b29 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -40,6 +40,7 @@ #include "server_settings.h" #include "specialist.h" #include "style.h" +#include "victory.h" /* victory_enabled() */ #include "requirements.h" @@ -4994,6 +4995,122 @@ bool are_reqs_active_ranges(const enum req_range min_range, return TRUE; } +/**********************************************************************//** + For requirements changing with time, will they be active for the target + after pass in period turns if nothing else changes? + Since year and calfrag changing is effect dependent, the result + may appear not precise. (Does not consider research progress etc.) +**************************************************************************/ +enum fc_tristate +tri_req_active_turns(int pass, int period, + const struct req_context *context, + const struct player *other_player, + const struct requirement *req) +{ + /* FIXME: doubles code from calendar.c */ + int ypt = get_world_bonus(EFT_TURN_YEARS); + int fpt = get_world_bonus(EFT_TURN_FRAGMENTS); + int fragment = game.info.fragment_count; + int fragment1 = fragment; /* if fragments don't advance */ + int year_inc, year_inc1; + const int slowdown = (victory_enabled(VC_SPACERACE) + ? get_world_bonus(EFT_SLOW_DOWN_TIMELINE) : 0); + bool present, present1; + + fc_assert(pass >= 0 && period >= 0); + if (slowdown >= 3) { + if (ypt > 1) { + ypt = 1; + } + } else if (slowdown >= 2) { + if (ypt > 2) { + ypt = 2; + } + } else if (slowdown >= 1) { + if (ypt > 5) { + ypt = 5; + } + } + year_inc = ypt * pass; + year_inc1 = year_inc + ypt * period; + if (game.calendar.calendar_fragments) { + int fragment_years; + + fragment += fpt * pass; + fragment_years = fragment / game.calendar.calendar_fragments; + year_inc += fragment_years; + fragment -= fragment_years * game.calendar.calendar_fragments; + fragment1 = fragment + fpt * period; + fragment_years += fragment1 / game.calendar.calendar_fragments; + year_inc1 += fragment_years; + fragment1 -= fragment_years * game.calendar.calendar_fragments; + } + if (game.calendar.calendar_skip_0 && game.info.year < 0) { + if (year_inc + game.info.year >= 0) { + year_inc++; + year_inc1++; + } else if (year_inc1 + game.info.year >= 0) { + year_inc1++; + } + } + + switch (req->source.kind) { + case VUT_AGE: + switch (req->range) { + case REQ_RANGE_LOCAL: + if (context->unit == NULL || !is_server()) { + return TRI_MAYBE; + } else { + int ua = game.info.turn + pass - context->unit->server.birth_turn; + + present = req->source.value.age <= ua; + present1 = req->source.value.age <= ua + period; + } + break; + case REQ_RANGE_CITY: + if (context->city == NULL) { + return TRI_MAYBE; + } else { + int ca = game.info.turn + pass - context->city->turn_founded; + + present = req->source.value.age <= ca; + present1 = req->source.value.age <= ca + period; + } + break; + case REQ_RANGE_PLAYER: + if (context->player == NULL) { + return TRI_MAYBE; + } else { + present = req->source.value.age + <= player_age(context->player) + pass; + present1 = req->source.value.age + <= player_age(context->player) + pass + period; + } + break; + default: + return TRI_MAYBE; + } + break; + case VUT_MINYEAR: + present = game.info.year + year_inc >= req->source.value.minyear; + present1 = game.info.year + year_inc1 >= req->source.value.minyear; + break; + case VUT_MINCALFRAG: + if (game.calendar.calendar_fragments <= period) { + /* Hope that the requirement is valid and fragments advance fine */ + return TRI_YES; + } + present = fragment >= req->source.value.mincalfrag; + present1 = fragment1 >= req->source.value.mincalfrag; + break; + default: + /* No special handling invented */ + return tri_req_active(context, other_player, req); + } + return BOOL_TO_TRISTATE(req->present + ? present || present1 : !(present && present1)); +} + /**********************************************************************//** A tester callback for tri_reqs_cb_active() that uses the default requirement calculation except for requirements in data[n_data] array diff --git a/common/requirements.h b/common/requirements.h index fa7e7881e2..1acf7ef1dc 100644 --- a/common/requirements.h +++ b/common/requirements.h @@ -176,6 +176,11 @@ bool are_reqs_active_ranges(const enum req_range min_range, const struct player *other_player, const struct requirement_vector *reqs, const enum req_problem_type prob_type); +enum fc_tristate +tri_req_active_turns(int pass, int period, + const struct req_context *context, + const struct player *other_player, + const struct requirement *req); /* Type of a callback that tests requirements due to a context * and something else in some manner different from tri_req_active() */ -- 2.37.2