From 5fda470d0fe9c89f695d9e83c753f718381efdeb Mon Sep 17 00:00:00 2001 From: Marko Lindqvist Date: Tue, 5 Jul 2022 03:13:48 +0300 Subject: [PATCH 51/51] Introduce unit_tech_reqs_iterate() and friends Use them instead of accessing unit type's require_advance directly. For most users that rework means that they would support multiple tech requirements for units, if unit_tech_reqs_iterate() just would give them. See osdn #44930 Signed-off-by: Marko Lindqvist --- ai/default/aidiplomat.c | 14 ++--- ai/default/aiparatrooper.c | 60 +++++++++------------ ai/default/aitech.c | 58 +++++++++++++-------- ai/default/aiunit.c | 2 +- ai/default/daimilitary.c | 63 +++++++++++++--------- ai/default/daisettler.c | 19 ++++--- client/gui-gtk-3.22/helpdlg.c | 12 ++--- client/gui-gtk-4.0/helpdlg.c | 11 ++-- client/gui-qt/citydlg.cpp | 3 +- client/gui-qt/helpdlg.cpp | 12 +++-- client/gui-qt/repodlgs.cpp | 4 +- client/gui-sdl2/action_dialog.c | 2 +- client/gui-sdl2/helpdlg.c | 28 +++++----- client/gui-sdl2/repodlgs.c | 13 ++--- client/helpdata.c | 34 +++++++++--- client/packhand.c | 2 +- client/reqtree.c | 20 ++++--- common/unittype.c | 92 ++++++++++++++++++++++----------- common/unittype.h | 18 ++++++- server/cityturn.c | 43 +++++++++++---- server/ruleset.c | 25 +++++---- tools/ruledit/edit_utype.cpp | 4 +- tools/ruledit/validity.c | 2 +- tools/ruleutil/rulesave.c | 2 +- 24 files changed, 332 insertions(+), 211 deletions(-) diff --git a/ai/default/aidiplomat.c b/ai/default/aidiplomat.c index f51841df81..744335c5b7 100644 --- a/ai/default/aidiplomat.c +++ b/ai/default/aidiplomat.c @@ -146,12 +146,14 @@ void dai_choose_diplomat_defensive(struct ai_type *ait, if (ut) { struct ai_plr *plr_data = def_ai_player_data(pplayer, ait); - plr_data->tech_want[advance_index(ut->require_advance)] - += DIPLO_DEFENSE_WANT; - TECH_LOG(ait, LOG_DEBUG, pplayer, ut->require_advance, - "ai_choose_diplomat_defensive() + %d for %s", - DIPLO_DEFENSE_WANT, - utype_rule_name(ut)); + unit_tech_reqs_iterate(ut, padv) { + plr_data->tech_want[advance_index(padv)] + += DIPLO_DEFENSE_WANT; + TECH_LOG(ait, LOG_DEBUG, pplayer, padv, + "ai_choose_diplomat_defensive() + %d for %s", + DIPLO_DEFENSE_WANT, + utype_rule_name(ut)); + } unit_tech_reqs_iterate_end; } } } diff --git a/ai/default/aiparatrooper.c b/ai/default/aiparatrooper.c index 06ed22ea9e..3dba4edc59 100644 --- a/ai/default/aiparatrooper.c +++ b/ai/default/aiparatrooper.c @@ -380,12 +380,8 @@ void dai_choose_paratrooper(struct ai_type *ait, struct player *pplayer, struct city *pcity, struct adv_choice *choice, bool allow_gold_upkeep) { - const struct research *presearch; + const struct research *presearch = research_get(pplayer); int profit; - Tech_type_id tech_req; - Tech_type_id requirements[A_LAST]; - int num_requirements = 0; - int i; struct ai_plr *plr_data = def_ai_player_data(pplayer, ait); /* military_advisor_choose_build does something idiotic, @@ -401,7 +397,8 @@ void dai_choose_paratrooper(struct ai_type *ait, && !utype_can_do_action(u_type, ACTION_PARADROP_CONQUER)) { continue; } - if (A_NEVER == u_type->require_advance) { + + if (A_NEVER == u_type->_retire.require_advance) { continue; } @@ -416,17 +413,28 @@ void dai_choose_paratrooper(struct ai_type *ait, } /* Assign tech for paratroopers */ - tech_req = advance_index(u_type->require_advance); - if (tech_req != A_NONE && tech_req != A_UNSET) { - for (i = 0; i < num_requirements; i++) { - if (requirements[i] == tech_req) { - break; - } - } - if (i == num_requirements) { - requirements[num_requirements++] = tech_req; + unit_tech_reqs_iterate(u_type, padv) { + /* We raise want if the required tech is not known */ + Tech_type_id tech_req = advance_index(padv); + + if (research_invention_state(presearch, tech_req) != TECH_KNOWN) { + plr_data->tech_want[tech_req] += 2; + log_base(LOGLEVEL_PARATROOPER, "Raising tech want in city %s for %s " + "stimulating %s with %d (" ADV_WANT_PRINTF ") and req", + city_name_get(pcity), + player_name(pplayer), + advance_rule_name(padv), + 2, + plr_data->tech_want[tech_req]); + + /* Now, we raise want for prerequisites */ + advance_index_iterate(A_FIRST, k) { + if (research_goal_tech_req(presearch, tech_req, k)) { + plr_data->tech_want[k] += 1; + } + } advance_index_iterate_end; } - } + } unit_tech_reqs_iterate_end; /* we only update choice struct if we can build it! */ if (!can_city_build_unit_now(pcity, u_type)) { @@ -453,25 +461,5 @@ void dai_choose_paratrooper(struct ai_type *ait, } } unit_type_iterate_end; - /* we raise want if the required tech is not known */ - presearch = research_get(pplayer); - for (i = 0; i < num_requirements; i++) { - tech_req = requirements[i]; - plr_data->tech_want[tech_req] += 2; - log_base(LOGLEVEL_PARATROOPER, "Raising tech want in city %s for %s " - "stimulating %s with %d (" ADV_WANT_PRINTF ") and req", - city_name_get(pcity), - player_name(pplayer), - advance_rule_name(advance_by_number(tech_req)), - 2, - plr_data->tech_want[tech_req]); - - /* now, we raise want for prerequisites */ - advance_index_iterate(A_FIRST, k) { - if (research_goal_tech_req(presearch, tech_req, k)) { - plr_data->tech_want[k] += 1; - } - } advance_index_iterate_end; - } return; } diff --git a/ai/default/aitech.c b/ai/default/aitech.c index 21540d0c4c..451ea4ff2c 100644 --- a/ai/default/aitech.c +++ b/ai/default/aitech.c @@ -415,15 +415,23 @@ struct unit_type *dai_wants_defender_against(struct ai_type *ait, /* It would be better than current best. Consider researching tech */ const struct impr_type *building; int cost = 0; - struct advance *itech = deftype->require_advance; + struct advance *itech = A_NEVER; bool impossible_to_get = FALSE; - if (A_NEVER != itech - && research_invention_state(presearch, - advance_number(itech)) != TECH_KNOWN) { - /* See if we want to invent this. */ - cost = research_goal_bulbs_required(presearch, - advance_number(itech)); + if (A_NEVER != deftype->_retire.require_advance) { + unit_tech_reqs_iterate(deftype, padv) { + if (research_invention_state(presearch, + advance_number(padv)) != TECH_KNOWN) { + /* See if we want to invent this. */ + int ucost = research_goal_bulbs_required(presearch, + advance_number(padv)); + + if (cost == 0 || ucost < cost) { + cost = ucost; + itech = padv; + } + } + } unit_tech_reqs_iterate_end; } building = utype_needs_improvement(deftype, pcity); @@ -450,9 +458,8 @@ struct unit_type *dai_wants_defender_against(struct ai_type *ait, * or the building's tech is cheaper, * go for the building's required tech. */ itech = preq->source.value.advance; - cost = 0; + cost = imprcost; } - cost += imprcost; } else if (!dai_can_requirement_be_met_in_city(preq, pplayer, pcity)) { impossible_to_get = TRUE; @@ -461,7 +468,7 @@ struct unit_type *dai_wants_defender_against(struct ai_type *ait, } requirement_vector_iterate_end; } - if (cost < best_cost && !impossible_to_get + if (cost < best_cost && !impossible_to_get && itech != A_NEVER && research_invention_reachable(presearch, advance_number(itech))) { best_tech = itech; best_cost = cost; @@ -491,8 +498,8 @@ struct unit_type *dai_wants_defender_against(struct ai_type *ait, } /**********************************************************************//** - Returns the best unit we can build, or NULL if none. "Best" here - means last in the unit list as defined in the ruleset. Assigns tech + Returns the best unit we can build, or NULL if none. "Best" here + means last in the unit list as defined in the ruleset. Assigns tech wants for techs to get better units with given role, but only for the cheapest to research "next" unit up the "chain". **************************************************************************/ @@ -509,21 +516,29 @@ struct unit_type *dai_wants_role_unit(struct ai_type *ait, struct player *pplaye n = num_role_units(role); for (i = n - 1; i >= 0; i--) { struct unit_type *iunit = get_role_unit(role, i); - struct advance *itech = iunit->require_advance; if (can_city_build_unit_now(pcity, iunit)) { build_unit = iunit; break; } else if (can_city_build_unit_later(pcity, iunit)) { const struct impr_type *building; + struct advance *itech = A_NEVER; int cost = 0; - if (A_NEVER != itech - && research_invention_state(presearch, - advance_number(itech)) != TECH_KNOWN) { - /* See if we want to invent this. */ - cost = research_goal_bulbs_required(presearch, - advance_number(itech)); + if (A_NEVER != iunit->_retire.require_advance) { + unit_tech_reqs_iterate(iunit, padv) { + if (research_invention_state(presearch, + advance_number(padv)) != TECH_KNOWN) { + /* See if we want to invent this. */ + int ucost = research_goal_bulbs_required(presearch, + advance_number(padv)); + + if (cost == 0 || ucost < cost) { + cost = ucost; + itech = padv; + } + } + } unit_tech_reqs_iterate_end; } building = utype_needs_improvement(iunit, pcity); @@ -543,15 +558,14 @@ struct unit_type *dai_wants_role_unit(struct ai_type *ait, struct player *pplaye * or the building's tech is cheaper, * go for the building's required tech. */ itech = preq->source.value.advance; - cost = 0; + cost = imprcost; } - cost += imprcost; } } } requirement_vector_iterate_end; } - if (cost < best_cost + if (cost < best_cost && itech != A_NEVER && research_invention_reachable(presearch, advance_number(itech))) { best_tech = itech; best_cost = cost; diff --git a/ai/default/aiunit.c b/ai/default/aiunit.c index 7e0808b86f..467223dacb 100644 --- a/ai/default/aiunit.c +++ b/ai/default/aiunit.c @@ -3142,7 +3142,7 @@ static void update_simple_ai_types(void) unit_type_iterate(punittype) { struct unit_class *pclass = utype_class(punittype); - if (A_NEVER != punittype->require_advance + if (A_NEVER != punittype->_retire.require_advance && !utype_has_flag(punittype, UTYF_CIVILIAN) && !utype_can_do_action(punittype, ACTION_SUICIDE_ATTACK) && !(pclass->adv.land_move == MOVE_NONE diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c index 36bc3adc1c..5779308426 100644 --- a/ai/default/daimilitary.c +++ b/ai/default/daimilitary.c @@ -889,9 +889,14 @@ bool dai_process_defender_want(struct ai_type *ait, struct player *pplayer, /* Cost (shield equivalent) of gaining these techs. */ /* FIXME? Katvrr advises that this should be weighted more heavily in * big danger. */ - int tech_cost = research_goal_bulbs_required(presearch, - advance_number(punittype->require_advance)) / 4 - / city_list_size(pplayer->cities); + int tech_cost = 0; + + unit_tech_reqs_iterate(punittype, padv) { + tech_cost = research_goal_bulbs_required(presearch, + advance_number(padv)); + } unit_tech_reqs_iterate_end; + + tech_cost = tech_cost / 4 / city_list_size(pplayer->cities); /* Contrary to the above, we don't care if walls are actually built * - we're looking into the future now. */ @@ -931,13 +936,15 @@ bool dai_process_defender_want(struct ai_type *ait, struct player *pplayer, * it is written this way, and the results seem strange to me. - Per */ int desire = tech_desire[utype_index(punittype)] * best_unit_cost / best; - plr_data->tech_want[advance_index(punittype->require_advance)] - += desire; - TECH_LOG(ait, LOG_DEBUG, pplayer, punittype->require_advance, - "+ %d for %s to defend %s", - desire, - utype_rule_name(punittype), - city_name_get(pcity)); + unit_tech_reqs_iterate(punittype, padv) { + plr_data->tech_want[advance_index(padv)] + += desire; + TECH_LOG(ait, LOG_DEBUG, pplayer, padv, + "+ %d for %s to defend %s", + desire, + utype_rule_name(punittype), + city_name_get(pcity)); + } unit_tech_reqs_iterate_end; } } simple_ai_unit_type_iterate_end; @@ -1012,9 +1019,6 @@ static void process_attacker_want(struct ai_type *ait, } simple_ai_unit_type_iterate(punittype) { - Tech_type_id tech_req = advance_number(punittype->require_advance); - int tech_dist = research_goal_unknown_techs(presearch, tech_req); - if (dai_can_unit_type_follow_unit_type(punittype, orig_utype, ait) && is_native_near_tile(&(wld.map), utype_class(punittype), ptile) && (U_NOT_OBSOLETED == punittype->obsoleted_by @@ -1038,14 +1042,25 @@ static void process_attacker_want(struct ai_type *ait, /* Cost (shield equivalent) of gaining these techs. */ /* FIXME? Katvrr advises that this should be weighted more heavily in big * danger. */ - int tech_cost = research_goal_bulbs_required(presearch, tech_req) / 4 - / city_list_size(pplayer->cities); + int tech_cost = 0; int bcost_balanced = build_cost_balanced(punittype); /* See description of kill_desire() for info about this variables. */ int bcost = utype_build_shield_cost(pcity, NULL, punittype); int attack = adv_unittype_att_rating(punittype, veteran_level, SINGLE_MOVE, punittype->hp); + int tech_dist = 0; + + unit_tech_reqs_iterate(punittype, padv) { + Tech_type_id tech_req = advance_number(padv); + + tech_cost += research_goal_bulbs_required(presearch, tech_req); + if (tech_dist == 0) { + tech_dist = research_goal_unknown_techs(presearch, tech_req); + } + } unit_tech_reqs_iterate_end; + + tech_cost = tech_cost / 4 / city_list_size(pplayer->cities); /* Take into account reinforcements strength */ if (acity) { @@ -1169,14 +1184,16 @@ static void process_attacker_want(struct ai_type *ait, if (want > 0) { if (tech_dist > 0) { /* This is a future unit, tell the scientist how much we need it */ - plr_data->tech_want[advance_index(punittype->require_advance)] - += want; - TECH_LOG(ait, LOG_DEBUG, pplayer, punittype->require_advance, - "+ " ADV_WANT_PRINTF " for %s vs %s(%d,%d)", - want, - utype_rule_name(punittype), - (acity ? city_name_get(acity) : utype_rule_name(victim_unit_type)), - TILE_XY(ptile)); + unit_tech_reqs_iterate(punittype, padv) { + plr_data->tech_want[advance_index(padv)] + += want; + TECH_LOG(ait, LOG_DEBUG, pplayer, padv, + "+ " ADV_WANT_PRINTF " for %s vs %s(%d,%d)", + want, + utype_rule_name(punittype), + (acity ? city_name_get(acity) : utype_rule_name(victim_unit_type)), + TILE_XY(ptile)); + } unit_tech_reqs_iterate_end; } else if (want > best_choice->want) { const struct impr_type *impr_req; diff --git a/ai/default/daisettler.c b/ai/default/daisettler.c index 2ae4c9e20a..94d4fcc8cf 100644 --- a/ai/default/daisettler.c +++ b/ai/default/daisettler.c @@ -923,17 +923,20 @@ static struct cityresult *find_best_city_placement(struct ai_type *ait, boattype = get_role_unit(L_FERRYBOAT, 0); if (NULL != boattype - && A_NEVER != boattype->require_advance) { + && A_NEVER != boattype->_retire.require_advance) { struct ai_plr *plr_data = def_ai_player_data(pplayer, ait); - plr_data->tech_want[advance_index(boattype->require_advance)] - += FERRY_TECH_WANT; - TECH_LOG(ait, LOG_DEBUG, pplayer, boattype->require_advance, - "+ %d for %s to ferry settler", - FERRY_TECH_WANT, - utype_rule_name(boattype)); + unit_tech_reqs_iterate(boattype, padv) { + plr_data->tech_want[advance_index(padv)] + += FERRY_TECH_WANT; + TECH_LOG(ait, LOG_DEBUG, pplayer, padv, + "+ %d for %s to ferry settler", + FERRY_TECH_WANT, + utype_rule_name(boattype)); + } unit_tech_reqs_iterate_end; } - /* return the result from the search on our current continent */ + + /* Return the result from the search on our current continent */ return cr1; } ferry = unit_virtual_create(pplayer, NULL, boattype, 0); diff --git a/client/gui-gtk-3.22/helpdlg.c b/client/gui-gtk-3.22/helpdlg.c index 6db2f53a80..4761c8effa 100644 --- a/client/gui-gtk-3.22/helpdlg.c +++ b/client/gui-gtk-3.22/helpdlg.c @@ -904,12 +904,8 @@ static void help_update_unit_type(const struct help_item *pitem, helptext_unit_upkeep_str(utype)); sprintf(buf, "%d", (int)sqrt((double)utype->vision_radius_sq)); gtk_label_set_text(GTK_LABEL(help_ulabel[3][4]), buf); - if (A_NEVER == utype->require_advance) { - gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]), REQ_LABEL_NEVER); - } else { - gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]), - advance_name_translation(utype->require_advance)); - } + gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]), + advance_name_translation(utype_primary_tech_req(utype))); /* create_tech_tree(help_improvement_tree, 0, advance_number(utype->require_advance), 3);*/ if (U_NOT_OBSOLETED == utype->obsoleted_by) { gtk_label_set_text(GTK_LABEL(help_ulabel[4][4]), skip_intl_qualifier_prefix(REQ_LABEL_NONE)); @@ -1073,9 +1069,11 @@ static void help_update_tech(const struct help_item *pitem, char *title) } improvement_iterate_end; unit_type_iterate(punittype) { - if (padvance != punittype->require_advance) { + + if (!is_tech_req_for_utype(punittype, padvance)) { continue; } + hbox = gtk_grid_new(); gtk_container_add(GTK_CONTAINER(help_vbox), hbox); w = gtk_label_new(_("Allows")); diff --git a/client/gui-gtk-4.0/helpdlg.c b/client/gui-gtk-4.0/helpdlg.c index a610c642a4..7a698eb6ad 100644 --- a/client/gui-gtk-4.0/helpdlg.c +++ b/client/gui-gtk-4.0/helpdlg.c @@ -937,12 +937,8 @@ static void help_update_unit_type(const struct help_item *pitem, helptext_unit_upkeep_str(utype)); sprintf(buf, "%d", (int)sqrt((double)utype->vision_radius_sq)); gtk_label_set_text(GTK_LABEL(help_ulabel[3][4]), buf); - if (A_NEVER == utype->require_advance) { - gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]), REQ_LABEL_NEVER); - } else { - gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]), - advance_name_translation(utype->require_advance)); - } + gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]), + advance_name_translation(utype_primary_tech_req(utype))); /* create_tech_tree(help_improvement_tree, 0, advance_number(utype->require_advance), 3);*/ if (U_NOT_OBSOLETED == utype->obsoleted_by) { gtk_label_set_text(GTK_LABEL(help_ulabel[4][4]), skip_intl_qualifier_prefix(REQ_LABEL_NONE)); @@ -1117,9 +1113,10 @@ static void help_update_tech(const struct help_item *pitem, char *title) unit_type_iterate(punittype) { int grid_col = 0; - if (padvance != punittype->require_advance) { + if (!is_tech_req_for_utype(punittype, padvance)) { continue; } + hbox = gtk_grid_new(); help_box_add(hbox); w = gtk_label_new(_("Allows")); diff --git a/client/gui-qt/citydlg.cpp b/client/gui-qt/citydlg.cpp index bd561140e2..c7e13b9885 100644 --- a/client/gui-qt/citydlg.cpp +++ b/client/gui-qt/citydlg.cpp @@ -3927,7 +3927,8 @@ QString get_tooltip_unit(const struct unit_type *utype, bool ext) + "\n"; obsolete = utype->obsoleted_by; if (obsolete) { - tech = obsolete->require_advance; + // FIXME: Don't give the impression that primary tech is (always) the only one. + tech = utype_primary_tech_req(obsolete); obsolete_str = QString(""); if (tech && tech != advance_by_number(0)) { /* TRANS: this and nearby literal strings are interpreted diff --git a/client/gui-qt/helpdlg.cpp b/client/gui-qt/helpdlg.cpp index 4578beebbf..46f0143dba 100644 --- a/client/gui-qt/helpdlg.cpp +++ b/client/gui-qt/helpdlg.cpp @@ -918,8 +918,8 @@ void help_widget::set_topic_unit(const help_item *topic, add_info_separator(); // Tech requirement - tech = utype->require_advance; - if (tech && tech != advance_by_number(0)) { + tech = utype_primary_tech_req(utype); + if (tech && advance_number(tech) != A_NONE) { QLabel *tb; tb = new QLabel(this); @@ -942,8 +942,8 @@ void help_widget::set_topic_unit(const help_item *topic, // Obsolescence obsolete = utype->obsoleted_by; if (obsolete) { - tech = obsolete->require_advance; - if (tech && tech != advance_by_number(0)) { + tech = utype_primary_tech_req(obsolete); + if (tech && advance_number(tech) != A_NONE) { QLabel *tb; tb = new QLabel(this); @@ -1154,9 +1154,11 @@ void help_widget::set_topic_tech(const help_item *topic, } improvement_iterate_end; unit_type_iterate(punittype) { - if (padvance != punittype->require_advance) { + + if (!is_tech_req_for_utype(punittype, padvance)) { continue; } + str = _("Allows"); str = "" + str + " " + link_me(utype_name_translation(punittype), HELP_UNIT); diff --git a/client/gui-qt/repodlgs.cpp b/client/gui-qt/repodlgs.cpp index 46a2c51846..8c1cf1e245 100644 --- a/client/gui-qt/repodlgs.cpp +++ b/client/gui-qt/repodlgs.cpp @@ -620,9 +620,11 @@ void research_diagram::create_tooltip_help() if (gui_options.reqtree_show_icons) { unit_type_iterate(unit) { - if (advance_number(unit->require_advance) != node->tech) { + + if (!is_tech_req_for_utype(unit, advance_by_number(node->tech))) { continue; } + sprite = get_unittype_sprite(tileset, unit, direction8_invalid()); get_sprite_dimensions(sprite, &swidth, &sheight); rttp = new req_tooltip_help(); diff --git a/client/gui-sdl2/action_dialog.c b/client/gui-sdl2/action_dialog.c index 77940aea06..4c7b962e07 100644 --- a/client/gui-sdl2/action_dialog.c +++ b/client/gui-sdl2/action_dialog.c @@ -481,7 +481,7 @@ static int spy_steal_popup_shared(struct widget *pwidget) /* Get Spy tech to use for "At Spy's Discretion" -- this will have the * side effect of displaying the unit's icon */ - tech = advance_number(unit_type_get(actor_unit)->require_advance); + tech = advance_number(utype_primary_tech_req(unit_type_get(actor_unit))); if (action_prob_possible(actor_unit->client.act_prob_cache[ get_non_targeted_action_id(act_id)])) { diff --git a/client/gui-sdl2/helpdlg.c b/client/gui-sdl2/helpdlg.c index a70286c8f5..bd2e559b9e 100644 --- a/client/gui-sdl2/helpdlg.c +++ b/client/gui-sdl2/helpdlg.c @@ -640,6 +640,7 @@ void popup_unit_info(Unit_type_id type_id) struct unit_type *punittype; char buffer[bufsz]; SDL_Rect area; + struct advance *req; if (current_help_dlg != HELP_UNIT) { popdown_help_dialog(); @@ -843,7 +844,7 @@ void popup_unit_info(Unit_type_id type_id) dock = unit_info_label; } - /* requirement */ + /* Requirement */ requirement_label = create_iconlabel_from_chars(NULL, pwindow->dst, _("Requirement:"), adj_font(12), 0); @@ -851,18 +852,22 @@ void popup_unit_info(Unit_type_id type_id) widget_add_as_prev(requirement_label, dock); dock = requirement_label; - if (A_NEVER == punittype->require_advance - || advance_by_number(A_NONE) == punittype->require_advance) { + req = utype_primary_tech_req(punittype); + + if (A_NEVER == punittype->_retire.require_advance + || advance_number(req) == A_NONE) { requirement_label2 = create_iconlabel_from_chars(NULL, pwindow->dst, Q_("?tech:None"), adj_font(12), 0); requirement_label2->id = ID_LABEL; } else { + Tech_type_id req_id = advance_number(req); + requirement_label2 = create_iconlabel_from_chars(NULL, pwindow->dst, - advance_name_translation(punittype->require_advance), + advance_name_translation(req), adj_font(12), WF_RESTORE_BACKGROUND); - requirement_label2->id = MAX_ID - advance_number(punittype->require_advance); - requirement_label2->string_utf8->fgcol = *get_tech_color(advance_number(punittype->require_advance)); + requirement_label2->id = MAX_ID - req_id; + requirement_label2->string_utf8->fgcol = *get_tech_color(req_id); requirement_label2->action = change_tech_callback; set_wstate(requirement_label2, FC_WS_NORMAL); } @@ -885,13 +890,14 @@ void popup_unit_info(Unit_type_id type_id) obsolete_by_label2->id = ID_LABEL; } else { const struct unit_type *utype = punittype->obsoleted_by; + struct advance *obs_req = utype_primary_tech_req(utype); obsolete_by_label2 = create_iconlabel_from_chars(NULL, pwindow->dst, utype_name_translation(utype), adj_font(12), WF_RESTORE_BACKGROUND); - obsolete_by_label2->string_utf8->fgcol = *get_tech_color(advance_number(utype->require_advance)); - obsolete_by_label2->id = MAX_ID - utype_number(punittype->obsoleted_by); + obsolete_by_label2->string_utf8->fgcol = *get_tech_color(advance_number(obs_req)); + obsolete_by_label2->id = MAX_ID - utype_number(utype); obsolete_by_label2->action = change_unit_callback; set_wstate(obsolete_by_label2, FC_WS_NORMAL); } @@ -1256,13 +1262,11 @@ static struct widget *create_tech_info(Tech_type_id tech, int width, unit_count = 0; unit_type_iterate(un) { - struct unit_type *punittype = un; - - if (advance_number(punittype->require_advance) == tech) { + if (is_tech_req_for_utype(un, advance_by_number(tech))) { pwidget = create_iconlabel_from_chars( resize_surface_box(get_unittype_surface(un, direction8_invalid()), adj_size(48), adj_size(48), 1, TRUE, TRUE), - pwindow->dst, utype_name_translation(punittype), adj_font(14), + pwindow->dst, utype_name_translation(un), adj_font(14), (WF_FREE_THEME | WF_RESTORE_BACKGROUND | WF_SELECT_WITHOUT_BAR)); set_wstate(pwidget, FC_WS_NORMAL); pwidget->action = change_unit_callback; diff --git a/client/gui-sdl2/repodlgs.c b/client/gui-sdl2/repodlgs.c index a576c6e50f..f81ec3d5cf 100644 --- a/client/gui-sdl2/repodlgs.c +++ b/client/gui-sdl2/repodlgs.c @@ -2413,7 +2413,6 @@ static struct advanced_dialog *change_tech_dlg = NULL; SDL_Surface *create_select_tech_icon(utf8_str *pstr, Tech_type_id tech_id, enum tech_info_mode mode) { - struct unit_type *punit = NULL; SDL_Surface *surf, *text, *tmp, *tmp2; SDL_Surface *surf_array[10], **buf_array; SDL_Rect dst; @@ -2526,8 +2525,7 @@ SDL_Surface *create_select_tech_icon(utf8_str *pstr, Tech_type_id tech_id, /* -------------------------------------------------------- */ w = 0; unit_type_iterate(un) { - punit = un; - if (advance_number(punit->require_advance) == tech_id) { + if (is_tech_req_for_utype(un, advance_by_number(tech_id))) { surf_array[w++] = adj_surf(get_unittype_surface(un, direction8_invalid())); } } unit_type_iterate_end; @@ -2618,7 +2616,6 @@ void real_science_report_dialog_update(void *unused) SDL_Surface *colb_surface = icons->big_colb; int step, i, cost; SDL_Rect dest; - struct unit_type *punit; struct widget *change_research_button; struct widget *change_research_goal_button; SDL_Rect area; @@ -2745,10 +2742,9 @@ void real_science_report_dialog_update(void *unused) dest.x += adj_size(5); - /* units */ + /* Units */ unit_type_iterate(un) { - punit = un; - if (advance_number(punit->require_advance) == presearch->researching) { + if (is_tech_req_for_utype(un, advance_by_number(presearch->researching))) { SDL_Surface *usurf = get_unittype_surface(un, direction8_invalid()); int w = usurf->w; @@ -2825,8 +2821,7 @@ void real_science_report_dialog_update(void *unused) /* units */ unit_type_iterate(un) { - punit = un; - if (advance_number(punit->require_advance) == presearch->tech_goal) { + if (is_tech_req_for_utype(un, advance_by_number(presearch->tech_goal))) { SDL_Surface *usurf = get_unittype_surface(un, direction8_invalid()); int w = usurf->w; diff --git a/client/helpdata.c b/client/helpdata.c index a9a6725c61..b08759ae07 100644 --- a/client/helpdata.c +++ b/client/helpdata.c @@ -1363,14 +1363,32 @@ char *helptext_building(char *buf, size_t bufsz, struct player *pplayer, } action_list_iterate_end; if (u) { - if (advance_number(u->require_advance) != A_NONE) { - cat_snprintf(buf, bufsz, - /* TRANS: 'Allows all players with knowledge of atomic - * power to build nuclear units.' */ - _("%s Allows all players with knowledge of %s " - "to build %s units.\n"), BULLET, - advance_name_translation(u->require_advance), - utype_name_translation(u)); + struct advance *req = NULL; + int count = 0; + + unit_tech_reqs_iterate(u, preq) { + req = preq; + count++; + } unit_tech_reqs_iterate_end; + + if (req != NULL) { + if (count == 1) { + cat_snprintf(buf, bufsz, + /* TRANS: 'Allows all players with knowledge of atomic + * power to build nuclear units.' */ + _("%s Allows all players with knowledge of %s " + "to build %s units.\n"), BULLET, + advance_name_translation(req), + utype_name_translation(u)); + } else { + /* Multiple tech requirements */ + cat_snprintf(buf, bufsz, + /* TRANS: 'Allows all players with knowledge of required + * techs to build nuclear units.' */ + _("%s Allows all players with knowledge of required " + "techs to build %s units.\n"), BULLET, + utype_name_translation(u)); + } } else { cat_snprintf(buf, bufsz, /* TRANS: 'Allows all players to build nuclear units.' */ diff --git a/client/packhand.c b/client/packhand.c index a3f2b64c1f..0e13be52f9 100644 --- a/client/packhand.c +++ b/client/packhand.c @@ -3538,7 +3538,7 @@ void handle_ruleset_unit(const struct packet_ruleset_unit *p) u->attack_strength = p->attack_strength; u->defense_strength = p->defense_strength; u->move_rate = p->move_rate; - u->require_advance = advance_by_number(p->tech_requirement); + u->_retire.require_advance = advance_by_number(p->tech_requirement); for (i = 0; i < p->build_reqs_count; i++) { requirement_vector_append(&u->build_reqs, p->build_reqs[i]); } diff --git a/client/reqtree.c b/client/reqtree.c index 0d10ffabcb..0ad467c17e 100644 --- a/client/reqtree.c +++ b/client/reqtree.c @@ -178,11 +178,13 @@ static void node_rectangle_minimum_size(struct tree_node *node, icons_width_sum = 5; if (gui_options.reqtree_show_icons) { - /* units */ + /* Units */ unit_type_iterate(unit) { - if (advance_number(unit->require_advance) != node->tech) { + + if (!is_tech_req_for_utype(unit, advance_by_number(node->tech))) { continue; } + sprite = get_unittype_sprite(tileset, unit, direction8_invalid()); get_sprite_dimensions(sprite, &swidth, &sheight); max_icon_height = MAX(max_icon_height, sheight); @@ -1081,12 +1083,14 @@ void draw_reqtree(struct reqtree *tree, struct canvas *pcanvas, text); icon_startx = startx + 5; - if (gui_options.reqtree_show_icons) { - unit_type_iterate(unit) { - if (advance_number(unit->require_advance) != node->tech) { - continue; - } - sprite = get_unittype_sprite(tileset, unit, direction8_invalid()); + if (gui_options.reqtree_show_icons) { + unit_type_iterate(unit) { + + if (!is_tech_req_for_utype(unit, advance_by_number(node->tech))) { + continue; + } + + sprite = get_unittype_sprite(tileset, unit, direction8_invalid()); get_sprite_dimensions(sprite, &swidth, &sheight); canvas_put_sprite_full(pcanvas, icon_startx, diff --git a/common/unittype.c b/common/unittype.c index c14a24d18e..be7fac737b 100644 --- a/common/unittype.c +++ b/common/unittype.c @@ -2024,10 +2024,11 @@ bool can_player_build_unit_direct(const struct player *p, bool consider_reg_impr_req) { const struct req_context context = { .player = p, .unittype = punittype }; + bool barbarian = is_barbarian(p); fc_assert_ret_val(NULL != punittype, FALSE); - if (is_barbarian(p) + if (barbarian && !utype_has_role(punittype, L_BARBARIAN_BUILD) && !utype_has_role(punittype, L_BARBARIAN_BUILD_TECH)) { /* Barbarians can build only role units */ @@ -2044,7 +2045,7 @@ bool can_player_build_unit_direct(const struct player *p, } if (utype_has_flag(punittype, UTYF_BARBARIAN_ONLY) - && !is_barbarian(p)) { + && !barbarian) { /* Unit can be built by barbarians only and this player is * not barbarian */ return FALSE; @@ -2095,36 +2096,37 @@ bool can_player_build_unit_direct(const struct player *p, } } requirement_vector_iterate_end; - if (research_invention_state(research_get(p), - advance_number(punittype->require_advance)) - != TECH_KNOWN) { - if (!is_barbarian(p)) { - /* Normal players can never build units without knowing tech - * requirements. */ - return FALSE; - } - if (!utype_has_role(punittype, L_BARBARIAN_BUILD)) { - /* Even barbarian cannot build this unit without tech */ - - /* Unit has to have L_BARBARIAN_BUILD_TECH role - * In the beginning of this function we checked that - * barbarian player tries to build only role - * L_BARBARIAN_BUILD or L_BARBARIAN_BUILD_TECH units. */ - fc_assert_ret_val(utype_has_role(punittype, L_BARBARIAN_BUILD_TECH), - FALSE); - - /* Client does not know all the advances other players have - * got. So following gives wrong answer in the client. - * This is called at the client when received create_city - * packet for a barbarian city. City initialization tries - * to find L_FIRSTBUILD unit. */ - - if (!game.info.global_advances[advance_index(punittype->require_advance)]) { - /* Nobody knows required tech */ + unit_tech_reqs_iterate(punittype, padv) { + if (research_invention_state(research_get(p), advance_number(padv)) + != TECH_KNOWN) { + if (!barbarian) { + /* Normal players can never build units without knowing tech + * requirements. */ return FALSE; } + if (!utype_has_role(punittype, L_BARBARIAN_BUILD)) { + /* Even barbarian cannot build this unit without tech */ + + /* Unit has to have L_BARBARIAN_BUILD_TECH role + * In the beginning of this function we checked that + * barbarian player tries to build only role + * L_BARBARIAN_BUILD or L_BARBARIAN_BUILD_TECH units. */ + fc_assert_ret_val(utype_has_role(punittype, L_BARBARIAN_BUILD_TECH), + FALSE); + + /* Client does not know all the advances other players have + * got. So following gives wrong answer in the client. + * This is called at the client when received create_city + * packet for a barbarian city. City initialization tries + * to find L_FIRSTBUILD unit. */ + + if (!game.info.global_advances[advance_index(padv)]) { + /* Nobody knows required tech */ + return FALSE; + } + } } - } + } unit_tech_reqs_iterate_end; if (utype_player_already_has_this_unique(p, punittype)) { /* A player can only have one unit of each unique unit type. */ @@ -2739,6 +2741,38 @@ void veteran_system_definition(struct veteran_system *vsystem, int level, vlevel->work_raise_chance = vlist_wraise; } +/**********************************************************************//** + Return primary tech requirement for the unit type. + Avoid using this, and support full list of requirements instead. + Returns pointer to A_NONE if there's no requirements for + the unit type. +**************************************************************************/ +struct advance *utype_primary_tech_req(const struct unit_type *ptype) +{ + unit_tech_reqs_iterate(ptype, padv) { + /* Return the very first! */ + return padv; + } unit_tech_reqs_iterate_end; + + /* There was no tech requirements */ + return advance_by_number(A_NONE); +} + +/**********************************************************************//** + Tell if the given tech is one of unit's tech requirements +**************************************************************************/ +bool is_tech_req_for_utype(const struct unit_type *ptype, + struct advance *padv) +{ + unit_tech_reqs_iterate(ptype, preq) { + if (padv == preq) { + return TRUE; + } + } unit_tech_reqs_iterate_end; + + return FALSE; +} + /**********************************************************************//** Return pointer to ai data of given unit type and ai type. **************************************************************************/ diff --git a/common/unittype.h b/common/unittype.h index 81ca57e6b7..89a963c7e7 100644 --- a/common/unittype.h +++ b/common/unittype.h @@ -497,7 +497,10 @@ struct unit_type { int move_rate; int unknown_move_cost; /* See utype_unknown_move_cost(). */ - struct advance *require_advance; /* may be NULL */ + struct { + struct advance *require_advance; /* may be NULL */ + } _retire; /* Do not write new code to rely on things here! */ + struct requirement_vector build_reqs; int vision_radius_sq; @@ -848,6 +851,19 @@ const struct unit_type *unit_type_array_last(void); } \ } unit_type_iterate_end; +#define unit_tech_reqs_iterate(_utype_, _p) \ +do { \ + struct advance *_p = (_utype_)->_retire.require_advance; \ + if (advance_number(_p) != A_NONE && _p != A_NEVER) { + +#define unit_tech_reqs_iterate_end \ + } \ +} while (FALSE); + +/* Used on client to show just one req */ +struct advance *utype_primary_tech_req(const struct unit_type *ptype); +bool is_tech_req_for_utype(const struct unit_type *ptype, + struct advance *padv); void *utype_ai_data(const struct unit_type *ptype, const struct ai_type *ai); void utype_set_ai_data(struct unit_type *ptype, const struct ai_type *ai, diff --git a/server/cityturn.c b/server/cityturn.c index d462a57d0d..00cb254891 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -2012,17 +2012,38 @@ static bool worklist_change_build_target(struct player *pplayer, /* Maybe we can just upgrade the target to what the city /can/ build. */ if (U_NOT_OBSOLETED == pupdate) { - /* Nope, we're stuck. Skip this item from the worklist. */ - if (ptarget->require_advance != NULL - && TECH_KNOWN != research_invention_state - (research_get(pplayer), - advance_number(ptarget->require_advance))) { - notify_player(pplayer, city_tile(pcity), - E_CITY_CANTBUILD, ftc_server, - _("%s can't build %s from the worklist; " - "tech %s not yet available. Postponing..."), - city_link(pcity), utype_name_translation(ptarget), - advance_name_translation(ptarget->require_advance)); + /* Nope, we're stuck. Skip this item from the worklist. */ + struct research *presearch = research_get(pplayer); + struct advance *missing = NULL; + bool multiple = FALSE; + + unit_tech_reqs_iterate(ptarget, padv) { + if (research_invention_state(presearch, advance_number(padv) != TECH_KNOWN)) { + if (missing != NULL) { + multiple = TRUE; + } else { + missing = padv; + } + } + } unit_tech_reqs_iterate_end; + + + if (missing != NULL) { + if (!multiple) { + notify_player(pplayer, city_tile(pcity), + E_CITY_CANTBUILD, ftc_server, + _("%s can't build %s from the worklist; " + "tech %s not yet available. Postponing..."), + city_link(pcity), utype_name_translation(ptarget), + advance_name_translation(missing)); + } else { + notify_player(pplayer, city_tile(pcity), + E_CITY_CANTBUILD, ftc_server, + _("%s can't build %s from the worklist; " + "multiple techs still needed. Postponing..."), + city_link(pcity), utype_name_translation(ptarget)); + } + script_server_signal_emit("unit_cant_be_built", ptarget, pcity, "need_tech"); } else { diff --git a/server/ruleset.c b/server/ruleset.c index 8e596f98e2..da326b20e0 100644 --- a/server/ruleset.c +++ b/server/ruleset.c @@ -2104,13 +2104,13 @@ static bool load_ruleset_units(struct section_file *file, const struct section *psection = section_list_get(sec, i); const char *sec_name = section_name(psection); - if (!lookup_tech(file, &u->require_advance, sec_name, + if (!lookup_tech(file, &u->_retire.require_advance, sec_name, "tech_req", filename, rule_name_get(&u->name))) { ok = FALSE; break; } - if (u->require_advance == A_NEVER) { + if (u->_retire.require_advance == A_NEVER) { ruleset_error(LOG_ERROR, "%s lacks valid tech_req.", rule_name_get(&u->name)); ok = FALSE; @@ -2525,12 +2525,17 @@ static bool load_ruleset_units(struct section_file *file, if (ok) { /* Some more consistency checking: */ unit_type_iterate(u) { - if (!valid_advance(u->require_advance)) { - ruleset_error(LOG_ERROR, "\"%s\" unit_type \"%s\": depends on removed tech \"%s\".", - filename, utype_rule_name(u), - advance_rule_name(u->require_advance)); - u->require_advance = A_NEVER; - ok = FALSE; + unit_tech_reqs_iterate(u, padv) { + if (!valid_advance(padv)) { + ruleset_error(LOG_ERROR, "\"%s\" unit_type \"%s\": depends on removed tech \"%s\".", + filename, utype_rule_name(u), + advance_rule_name(padv)); + ok = FALSE; + break; + } + } unit_tech_reqs_iterate_end; + + if (!ok) { break; } } unit_type_iterate_end; @@ -7735,8 +7740,8 @@ static void send_ruleset_units(struct conn_list *dest) packet.attack_strength = u->attack_strength; packet.defense_strength = u->defense_strength; packet.move_rate = u->move_rate; - packet.tech_requirement = u->require_advance - ? advance_number(u->require_advance) + packet.tech_requirement = u->_retire.require_advance + ? advance_number(u->_retire.require_advance) : advance_count(); i = 0; diff --git a/tools/ruledit/edit_utype.cpp b/tools/ruledit/edit_utype.cpp index a39ede626a..a39b09559e 100644 --- a/tools/ruledit/edit_utype.cpp +++ b/tools/ruledit/edit_utype.cpp @@ -121,7 +121,7 @@ void edit_utype::closeEvent(QCloseEvent *cevent) **************************************************************************/ void edit_utype::refresh() { - req_button->setText(tab_tech::tech_name(utype->require_advance)); + req_button->setText(tab_tech::tech_name(utype->_retire.require_advance)); bcost->setValue(utype->build_cost); attack->setValue(utype->attack_strength); defense->setValue(utype->defense_strength); @@ -140,7 +140,7 @@ void edit_utype::req_menu(QAction *action) padv = advance_by_rule_name(an_bytes.data()); if (padv != nullptr) { - utype->require_advance = padv; + utype->_retire.require_advance = padv; refresh(); } diff --git a/tools/ruledit/validity.c b/tools/ruledit/validity.c index 4982130e6d..62d9410292 100644 --- a/tools/ruledit/validity.c +++ b/tools/ruledit/validity.c @@ -191,7 +191,7 @@ bool is_tech_needed(struct advance *padv, requirers_cb cb, void *data) } advance_re_active_iterate_end; unit_type_re_active_iterate(ptype) { - if (ptype->require_advance == padv) { + if (is_tech_req_for_utype(ptype, padv)) { cb(utype_rule_name(ptype), data); needed = TRUE; } diff --git a/tools/ruleutil/rulesave.c b/tools/ruleutil/rulesave.c index 060fc4aaf7..217981c5fc 100644 --- a/tools/ruleutil/rulesave.c +++ b/tools/ruleutil/rulesave.c @@ -3060,7 +3060,7 @@ static bool save_units_ruleset(const char *filename, const char *name) secfile_insert_str(sfile, uclass_rule_name(put->uclass), "%s.class", path); - save_tech_ref(sfile, put->require_advance, path, "tech_req"); + save_tech_ref(sfile, put->_retire.require_advance, path, "tech_req"); /* Extract the government requirement from the requirement vector so * it can be written in the old format. -- 2.35.1