diff -Nur -X../diff_ignore ../LT2_5/client/client_main.c ./client/client_main.c --- ../LT2_5/client/client_main.c 2019-01-19 17:40:00.451899163 +0300 +++ ./client/client_main.c 2019-08-01 23:37:42.793397996 +0300 @@ -147,6 +147,7 @@ bool auto_spawn = FALSE; /* TRUE = skip main menu, start local server */ bool in_ggz = FALSE; enum announce_type announce; +enum fc_tristate got_tech = TRI_MAYBE; struct civclient client; @@ -875,6 +876,7 @@ can_slide = FALSE; unit_focus_update(); can_slide = TRUE; + got_tech = TRI_MAYBE; /* No way know if the player has just got a tech */ set_client_page(PAGE_GAME); /* Find something sensible to display instead of the intro gfx. */ center_on_something(); diff -Nur -X../diff_ignore ../LT2_5/client/client_main.h ./client/client_main.h --- ../LT2_5/client/client_main.h 2019-01-19 17:40:00.451899163 +0300 +++ ./client/client_main.h 2019-08-01 23:37:42.885398827 +0300 @@ -37,7 +37,7 @@ * to a server. In this state, neither game nor ruleset * is in effect. * C_S_PREPARING: Connected in pregame. Game and ruleset are done. - * C_S_RUNNING: Connected ith game in progress. + * C_S_RUNNING: Connected with game in progress. * C_S_OVER: Connected with game over. */ enum client_states { @@ -80,6 +80,7 @@ extern bool auto_spawn; extern bool waiting_for_end_turn; extern bool in_ggz; +extern enum fc_tristate got_tech; struct global_worklist_list; /* Defined in global_worklist.[ch]. */ diff -Nur -X../diff_ignore ../LT2_5/client/gui-gtk-2.0/repodlgs.c ./client/gui-gtk-2.0/repodlgs.c --- ../LT2_5/client/gui-gtk-2.0/repodlgs.c 2019-01-19 17:40:00.467904144 +0300 +++ ./client/gui-gtk-2.0/repodlgs.c 2019-08-02 23:19:36.421363530 +0300 @@ -74,6 +74,8 @@ GtkProgressBar *progress_bar; GtkLabel *goal_label; GtkLayout *drawing_area; + GtkWidget *ch_now; + Tech_type_id target_tech; /* for ch_now dialog */ }; static GtkListStore *science_report_store_new(void); @@ -105,6 +107,9 @@ static struct science_report science_report = { NULL, }; static bool science_report_no_combo_callback = FALSE; +static void science_report_confirm_research(Tech_type_id tech); +static void confirm_change_response(GtkWidget *w, gint response, + gpointer data); /* Those values must match the function science_report_store_new(). */ enum science_report_columns { @@ -187,6 +192,143 @@ log_error("%s(): Tech %d not found in the combo.", __FUNCTION__, tech); } + +/************************************************************************//** + Handles changing research: if changing will have effect immediately, + asks to confirm and warns about bulb loss. +****************************************************************************/ +static void science_report_confirm_research(Tech_type_id tech) +{ + struct player_research *research = + (client_has_player() ? player_research_get(client_player()) : NULL);; + GtkWidget *chpop; + int bulbs, loss; + int cost = base_total_bulbs_required(client_player(), tech, FALSE); + int min_cost = (int) (cost * min_leakage_ratio(client_player(), tech)); + struct advance *vap = valid_advance_by_number(tech); + char buf[511]; + struct option *tpo; + + fc_assert(NULL != vap); + fc_assert(0 != cost); + + bulbs = (tech == research->researching_saved) + ? research->bulbs_researching_saved + : research->bulbs_researched; + + /* In some cases you have more bulbs than your current tech costs... */ + if (!(TRI_YES == got_tech) && (tech != research->researching_saved)) { + tpo = optset_option_by_name(server_optset, "techpenalty"); + + if (!tpo) { + log_error("techpenalty server option unknown"); + loss = 0; + } else { + loss = option_int_get(tpo) * bulbs / 100; + } + } else { + loss = 0; + } + fc_assert(bulbs >= loss); + if(!min_cost) { + min_cost = 1; + } + log_debug("Consider switching to %s with %d bulbs (%d|%d), %d losing, " + "%d..%d expecting to spend", + advance_rule_name(advance_by_number(tech)), bulbs, + research->bulbs_researching_saved, research->bulbs_researched, + loss, min_cost, cost); + + if ((TRI_NO == got_tech ? bulbs - loss : bulbs) >= min_cost) { + if (min_cost != cost) { + fc_snprintf(buf, sizeof(buf), + PL_("(The cost might be down to %d bulb" + " due to tech leakage.)\n", + "(The cost might be down to %d bulbs" + " due to tech leakage.)\n", + min_cost), + min_cost); + } else { + buf[0] = '\0'; + } + if (loss > 0) { + if (TRI_NO == got_tech) { + cat_snprintf(buf, sizeof(buf), + PL_("%d bulb will be lost.\n", + "%d bulbs will be lost.\n", loss), loss); + } else { + cat_snprintf(buf, sizeof(buf), + PL_("%d bulb may be lost " + "if you have got no advance this turn.\n", + "%d bulbs may be lost " + "if you have got no advance this turn.\n", loss), + loss); + } + } + + if ((TRI_MAYBE != got_tech) && (min_cost == cost)) { + cat_snprintf(buf, sizeof(buf), + PL_("%d bulb will remain.\n", + "%d bulbs will remain.\n", bulbs - cost - loss), + bulbs - cost - loss); + } else { + int max_rem = (TRI_NO == got_tech + ? bulbs - min_cost - loss + : bulbs - min_cost); + int min_rem = (TRI_YES == got_tech + ? bulbs - cost + : bulbs - cost - loss); + cat_snprintf(buf, sizeof(buf), + /* FIXME: in some languages does PL_ act good here? */ + PL_("%d to %d bulb will remain.\n", + "%d to %d bulbs will remain.\n", + max_rem), + MAX(0, min_rem), + max_rem + ); + } + + chpop = gtk_message_dialog_new(NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_OK_CANCEL, + PL_("Research %s for %d bulb?\n%s", + "Research %s for %d bulbs?\n%s", cost), + advance_name_translation(vap), cost, buf); + setup_dialog(chpop, gui_dialog_get_toplevel(science_report.shell)); + + gtk_window_set_title(GTK_WINDOW(chpop), + (bulbs - loss >= cost) ? _("Research now!") + : _("Immediate research might happen!")); + gtk_window_set_position(GTK_WINDOW(chpop), GTK_WIN_POS_CENTER_ON_PARENT); + + science_report.ch_now = chpop; + science_report.target_tech = tech; + g_signal_connect(chpop, "response", + G_CALLBACK(confirm_change_response), &science_report); + + gtk_window_present(GTK_WINDOW(chpop)); + } else { + dsend_packet_player_research(&client.conn, tech); + } +} + +/**********************************************************************//** + User has responded to change research confirmation +**************************************************************************/ +static void confirm_change_response(GtkWidget *w, gint response, + gpointer data) +{ + struct science_report *pdialog = data; + + if (response == GTK_RESPONSE_OK) { + dsend_packet_player_research(&client.conn, pdialog->target_tech); + } + gtk_widget_destroy(w); + + pdialog->ch_now = NULL; +} + /**************************************************************************** Change tech goal, research or open help dialog. ****************************************************************************/ @@ -209,7 +351,8 @@ /* LMB: set research or research goal */ switch (player_invention_state(client_player(), tech)) { case TECH_PREREQS_KNOWN: - dsend_packet_player_research(&client.conn, tech); + /* If we can research target tech immediately, popup a dialog */ + science_report_confirm_research(tech); break; case TECH_UNKNOWN: dsend_packet_player_tech_goal(&client.conn, tech); @@ -473,7 +616,7 @@ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data))) { popup_help_dialog_typed(tech_name, HELP_TECH); } else if (can_client_issue_orders()) { - dsend_packet_player_research(&client.conn, tech); + science_report_confirm_research(tech); } /* Revert, or we will be not synchron with the server. */ science_report_combo_set_active(combo, player_research_get @@ -620,6 +763,8 @@ science_report_update(preport); gui_dialog_show_all(preport->shell); + preport->ch_now = NULL; + preport->target_tech = A_LAST; /* This must be _after_ the dialog is drawn to really center it ... */ science_report_redraw(preport); @@ -632,6 +777,9 @@ { fc_assert_ret(NULL != preport); + if (preport->ch_now) { + gtk_widget_destroy(preport->ch_now); + } gui_dialog_destroy(preport->shell); fc_assert(NULL == preport->shell); diff -Nur -X../diff_ignore ../LT2_5/common/tech.c ./common/tech.c --- ../LT2_5/common/tech.c 2019-01-19 17:40:00.511917844 +0300 +++ ./common/tech.c 2019-08-02 00:22:06.229454963 +0300 @@ -662,6 +662,63 @@ FALSE); } +/**********************************************************************//** + If leakage style is 2 or 3, it counts players that pplayer does not have + embassy to and returns how much cheaper the tech would be if they all + know it rather than if they all don't know +**************************************************************************/ +double min_leakage_ratio(const struct player *pplayer, + Tech_type_id tech) +{ + const struct player_research *presearch = player_research_get(pplayer); + fc_assert(presearch); + + switch (game.info.tech_leakage) { + case 0: + case 1: + return 1.0; + break; + case 2: + case 3: + { + int players = 0, players_with_tech = 0, players_obscure = 0; + + players_iterate_alive(aplayer) { + if ((3 == game.info.tech_leakage) && is_barbarian(aplayer)) { + continue; + } + players++; + if (player_has_embassy(pplayer, aplayer)) { + if (A_FUTURE == tech + ? (player_research_get(aplayer)->future_tech + > presearch->future_tech) + : TECH_KNOWN == player_invention_state(aplayer, tech)) { + players_with_tech++; + } + } else { + players_obscure++; + } + + } players_iterate_alive_end; + + fc_assert_ret_val(0 < players, 1.0); + fc_assert(players >= players_with_tech + players_obscure); + + if (0 == players_obscure) { + return 1.0; + } + return (double) (players - players_with_tech - players_obscure) + / (players - players_with_tech); + } + break; + + default: + log_error("Invalid tech_leakage %d", game.info.tech_leakage); + } + + return 1.0; +} + /**************************************************************************** Function to determine cost for technology. The equation is determined from game.info.tech_cost_style and game.info.tech_leakage. diff -Nur -X../diff_ignore ../LT2_5/common/tech.h ./common/tech.h --- ../LT2_5/common/tech.h 2019-01-19 17:40:00.511917844 +0300 +++ ./common/tech.h 2019-08-01 23:37:42.929399224 +0300 @@ -225,6 +225,8 @@ bool is_future_tech(Tech_type_id tech); void precalc_tech_data(void); +double min_leakage_ratio(const struct player *pplayer, + Tech_type_id tech); /* Initialization and iteration */ void techs_init(void);