From 5a9db2718487d385b5f0210abf338a402cc8cb00 Mon Sep 17 00:00:00 2001 From: hop311 Date: Sat, 22 Feb 2025 21:53:01 +0000 Subject: [PATCH] Add step size and fixed point functionality to GUIScrollbar for Trade and Budget menus --- extension/doc_classes/GUIScrollbar.xml | 14 +++ extension/doc_classes/MenuSingleton.xml | 7 ++ .../classes/GUIScrollbar.cpp | 91 ++++++++++++++++--- .../classes/GUIScrollbar.hpp | 25 ++++- .../singletons/MenuSingleton.cpp | 3 +- .../singletons/MenuSingleton.hpp | 10 +- .../singletons/TradeMenu.cpp | 27 +++++- .../NationManagementScreen/TradeMenu.gd | 57 +++++++----- 8 files changed, 187 insertions(+), 47 deletions(-) diff --git a/extension/doc_classes/GUIScrollbar.xml b/extension/doc_classes/GUIScrollbar.xml index 83fdf1d4..5e6b538c 100644 --- a/extension/doc_classes/GUIScrollbar.xml +++ b/extension/doc_classes/GUIScrollbar.xml @@ -73,6 +73,11 @@ + + + + + @@ -118,6 +123,15 @@ + + + + + + + + + diff --git a/extension/doc_classes/MenuSingleton.xml b/extension/doc_classes/MenuSingleton.xml index b96d465d..b0288df4 100644 --- a/extension/doc_classes/MenuSingleton.xml +++ b/extension/doc_classes/MenuSingleton.xml @@ -7,6 +7,12 @@ + + + + + + @@ -183,6 +189,7 @@ + diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.cpp b/extension/src/openvic-extension/classes/GUIScrollbar.cpp index f8d2e7b1..493babf5 100644 --- a/extension/src/openvic-extension/classes/GUIScrollbar.cpp +++ b/extension/src/openvic-extension/classes/GUIScrollbar.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "openvic-extension/utility/ClassBindings.hpp" #include "openvic-extension/utility/UITools.hpp" #include "openvic-extension/utility/Utilities.hpp" @@ -40,11 +42,15 @@ void GUIScrollbar::_bind_methods() { OV_BIND_METHOD(GUIScrollbar::increment_value, { "signal" }, DEFVAL(true)); OV_BIND_METHOD(GUIScrollbar::decrement_value, { "signal" }, DEFVAL(true)); OV_BIND_METHOD(GUIScrollbar::set_value_as_ratio, { "new_ratio", "signal" }, DEFVAL(true)); + OV_BIND_METHOD(GUIScrollbar::get_value_scaled); OV_BIND_METHOD(GUIScrollbar::is_range_limited); OV_BIND_METHOD(GUIScrollbar::get_range_limit_min); OV_BIND_METHOD(GUIScrollbar::get_range_limit_max); OV_BIND_METHOD(GUIScrollbar::set_range_limits, { "new_range_limit_min", "new_range_limit_max", "signal" }, DEFVAL(true)); + OV_BIND_METHOD(GUIScrollbar::set_range_limits_and_value, { + "new_range_limit_min", "new_range_limit_max", "new_value", "signal" + }, DEFVAL(true)); OV_BIND_METHOD(GUIScrollbar::set_limits, { "new_min_value", "new_max_value", "signal" }, DEFVAL(true)); OV_BIND_METHOD(GUIScrollbar::get_length_override); @@ -426,20 +432,9 @@ Error GUIScrollbar::set_gui_scrollbar(GUI::Scrollbar const* new_gui_scrollbar) { _calculate_rects(); - fixed_point_t step_size = gui_scrollbar->get_step_size(); - if (step_size <= 0) { - UtilityFunctions::push_error( - "Invalid step size ", Utilities::fixed_point_to_string_dp(step_size, -1), " for GUIScrollbar ", - gui_scrollbar_name, " - not positive! Defaulting to 1." - ); - step_size = 1; - ret = false; - } - min_value = gui_scrollbar->get_min_value() / step_size; - max_value = gui_scrollbar->get_max_value() / step_size; - - ret &= _constrain_limits() == OK; - ret &= reset() == OK; + ret &= set_step_size_and_limits_fp( + gui_scrollbar->get_step_size(), gui_scrollbar->get_min_value(), gui_scrollbar->get_max_value() + ) == OK; return ERR(ret); } @@ -472,6 +467,14 @@ void GUIScrollbar::set_value(int32_t new_value, bool signal) { } } +void GUIScrollbar::set_value_fp(fixed_point_t new_value, bool signal) { + return set_value(new_value / step_size, signal); +} + +void GUIScrollbar::set_value_from_slider_value(SliderValue const& slider_value, int32_t scale, bool signal) { + set_value_fp(slider_value.get_value() * scale, signal); +} + void GUIScrollbar::increment_value(bool signal) { set_value(value + 1, signal); } @@ -488,12 +491,26 @@ void GUIScrollbar::set_value_as_ratio(float new_ratio, bool signal) { set_value(min_value + (max_value - min_value) * new_ratio, signal); } +fixed_point_t GUIScrollbar::get_value_scaled_fp() const { + return value * step_size; +} + +float GUIScrollbar::get_value_scaled() const { + return get_value_scaled_fp().to_float(); +} + Error GUIScrollbar::set_range_limits(int32_t new_range_limit_min, int32_t new_range_limit_max, bool signal) { + return set_range_limits_and_value(new_range_limit_min, new_range_limit_max, value, signal); +} + +Error GUIScrollbar::set_range_limits_and_value( + int32_t new_range_limit_min, int32_t new_range_limit_max, int32_t new_value, bool signal +) { ERR_FAIL_COND_V_MSG(!range_limited, FAILED, "Cannot set range limits of non-range-limited GUIScrollbar!"); range_limit_min = new_range_limit_min; range_limit_max = new_range_limit_max; const Error err = _constrain_range_limits(); - set_value(value, signal); + set_value(new_value, signal); return err; } @@ -506,6 +523,50 @@ Error GUIScrollbar::set_limits(int32_t new_min_value, int32_t new_max_value, boo return ERR(ret); } +Error GUIScrollbar::set_range_limits_and_value_fp( + fixed_point_t new_range_limit_min, fixed_point_t new_range_limit_max, fixed_point_t new_value, bool signal +) { + return set_range_limits_and_value( + new_range_limit_min / step_size, + new_range_limit_max / step_size, + new_value / step_size, + signal + ); +} + +Error GUIScrollbar::set_range_limits_and_value_from_slider_value( + SliderValue const& slider_value, int32_t scale, bool signal +) { + return set_range_limits_and_value_fp( + slider_value.get_min() * scale, + slider_value.get_max() * scale, + slider_value.get_value() * scale, + signal + ); +} + +Error GUIScrollbar::set_step_size_and_limits_fp(fixed_point_t new_step_size, int32_t new_min_value, int32_t new_max_value) { + bool ret = true; + + step_size = new_step_size; + if (step_size <= 0) { + UtilityFunctions::push_error( + "Invalid step size ", Utilities::fixed_point_to_string_dp(step_size, -1), " for GUIScrollbar ", + get_name(), " - not positive! Defaulting to 1." + ); + step_size = fixed_point_t::_1(); + ret = false; + } + + min_value = new_min_value / step_size; + max_value = new_max_value / step_size; + + ret &= _constrain_limits() == OK; + ret &= reset() == OK; + + return ERR(ret); +} + void GUIScrollbar::set_length_override(real_t new_length_override) { ERR_FAIL_COND_MSG( length_override < 0, vformat("Invalid GUIScrollbar length override: %f - cannot be negative!", length_override) diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.hpp b/extension/src/openvic-extension/classes/GUIScrollbar.hpp index beba50b5..3760e966 100644 --- a/extension/src/openvic-extension/classes/GUIScrollbar.hpp +++ b/extension/src/openvic-extension/classes/GUIScrollbar.hpp @@ -9,6 +9,8 @@ #include "openvic-extension/classes/GUIHasTooltip.hpp" namespace OpenVic { + struct SliderValue; + class GUIScrollbar : public godot::Control { GDCLASS(GUIScrollbar, godot::Control) @@ -36,11 +38,12 @@ namespace OpenVic { godot::Orientation PROPERTY(orientation, godot::HORIZONTAL); real_t PROPERTY(length_override, 0.0); + fixed_point_t PROPERTY(step_size, fixed_point_t::_1()); int32_t PROPERTY(value, 0); int32_t PROPERTY(min_value, 0); int32_t PROPERTY(max_value, 0); - bool PROPERTY_CUSTOM_PREFIX(range_limited, is); + bool PROPERTY_CUSTOM_PREFIX(range_limited, is, false); int32_t PROPERTY(range_limit_min, 0); int32_t PROPERTY(range_limit_max, 0); @@ -97,15 +100,33 @@ namespace OpenVic { godot::String get_gui_scrollbar_name() const; void set_value(int32_t new_value, bool signal = true); + void set_value_fp(fixed_point_t new_value, bool signal = true); + void set_value_from_slider_value(SliderValue const& slider_value, int32_t scale, bool signal = true); void increment_value(bool signal = true); void decrement_value(bool signal = true); float get_value_as_ratio() const; void set_value_as_ratio(float new_ratio, bool signal = true); - godot::Error set_range_limits(int32_t new_range_limit_min, int32_t new_range_limit_max, bool signal = true); + fixed_point_t get_value_scaled_fp() const; + float get_value_scaled() const; + + godot::Error set_range_limits( + int32_t new_range_limit_min, int32_t new_range_limit_max, bool signal = true + ); + godot::Error set_range_limits_and_value( + int32_t new_range_limit_min, int32_t new_range_limit_max, int32_t new_value, bool signal = true + ); godot::Error set_limits(int32_t new_min_value, int32_t new_max_value, bool signal = true); + godot::Error set_range_limits_and_value_fp( + fixed_point_t new_range_limit_min, fixed_point_t new_range_limit_max, fixed_point_t new_value, bool signal = true + ); + godot::Error set_range_limits_and_value_from_slider_value( + SliderValue const& slider_value, int32_t scale, bool signal = true + ); + godot::Error set_step_size_and_limits_fp(fixed_point_t new_step_size, int32_t new_min_value, int32_t new_max_value); + /* Override the main dimension of gui_scollbar's size with the specified length. */ void set_length_override(real_t new_length_override); }; diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp index 9e62641f..faf28f19 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.cpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.cpp @@ -405,8 +405,9 @@ void MenuSingleton::_bind_methods() { /* TRADE MENU */ OV_BIND_METHOD(MenuSingleton::get_trade_menu_good_categories_info); - OV_BIND_METHOD(MenuSingleton::get_trade_menu_trade_details_info, { "trade_detail_good_index" }); + OV_BIND_METHOD(MenuSingleton::get_trade_menu_trade_details_info, { "trade_detail_good_index", "stockpile_cutoff_slider" }); OV_BIND_METHOD(MenuSingleton::get_trade_menu_tables_info); + OV_BIND_SMETHOD(calculate_trade_menu_stockpile_cutoff_amount, { "slider" }); BIND_ENUM_CONSTANT(TRADE_SETTING_NONE); BIND_ENUM_CONSTANT(TRADE_SETTING_AUTOMATED); diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.hpp b/extension/src/openvic-extension/singletons/MenuSingleton.hpp index 072327e6..9aee2131 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp @@ -26,6 +26,7 @@ namespace OpenVic { struct ModifierSum; struct RuleSet; struct LeaderInstance; + struct GUIScrollbar; class MenuSingleton : public godot::Object { GDCLASS(MenuSingleton, godot::Object) @@ -239,8 +240,15 @@ namespace OpenVic { /* TRADE MENU */ godot::Dictionary get_trade_menu_good_categories_info() const; - godot::Dictionary get_trade_menu_trade_details_info(int32_t trade_detail_good_index) const; + godot::Dictionary get_trade_menu_trade_details_info( + int32_t trade_detail_good_index, GUIScrollbar* stockpile_cutoff_slider + ) const; godot::Dictionary get_trade_menu_tables_info() const; + static constexpr fixed_point_t calculate_trade_menu_stockpile_cutoff_amount_fp(fixed_point_t value) { + // TODO - replace this with: pow(2001, value / 2000) - 1 + return value; + } + static float calculate_trade_menu_stockpile_cutoff_amount(GUIScrollbar const* slider); /* MILITARY MENU */ godot::Dictionary make_leader_dict(LeaderInstance const& leader); diff --git a/extension/src/openvic-extension/singletons/TradeMenu.cpp b/extension/src/openvic-extension/singletons/TradeMenu.cpp index e8bc8f6d..1618b633 100644 --- a/extension/src/openvic-extension/singletons/TradeMenu.cpp +++ b/extension/src/openvic-extension/singletons/TradeMenu.cpp @@ -3,6 +3,7 @@ #include #include "openvic-extension/classes/GUILabel.hpp" +#include "openvic-extension/classes/GUIScrollbar.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/singletons/PlayerSingleton.hpp" #include "openvic-extension/utility/Utilities.hpp" @@ -105,14 +106,15 @@ Dictionary MenuSingleton::get_trade_menu_good_categories_info() const { return ret; } -Dictionary MenuSingleton::get_trade_menu_trade_details_info(int32_t trade_detail_good_index) const { +Dictionary MenuSingleton::get_trade_menu_trade_details_info( + int32_t trade_detail_good_index, GUIScrollbar* stockpile_cutoff_slider +) const { static const StringName trade_detail_good_name_key = "trade_detail_good_name"; static const StringName trade_detail_good_price_key = "trade_detail_good_price"; static const StringName trade_detail_good_base_price_key = "trade_detail_good_base_price"; static const StringName trade_detail_price_history_key = "trade_detail_price_history"; static const StringName trade_detail_is_automated_key = "trade_detail_is_automated"; static const StringName trade_detail_is_selling_key = "trade_detail_is_selling"; // or buying (false) - static const StringName trade_detail_slider_value_key = "trade_detail_slider_value"; // linear slider value static const StringName trade_detail_slider_amount_key = "trade_detail_slider_amount"; // exponential good amount static const StringName trade_detail_government_needs_key = "trade_detail_government_needs"; static const StringName trade_detail_army_needs_key = "trade_detail_army_needs"; @@ -163,8 +165,19 @@ Dictionary MenuSingleton::get_trade_menu_trade_details_info(int32_t trade_detail ret[trade_detail_is_automated_key] = good_data.is_automated; ret[trade_detail_is_selling_key] = good_data.is_selling; - // TODO - use exponential formula! - ret[trade_detail_slider_value_key] = (good_data.stockpile_cutoff / 2000).to_int32_t(); + if (stockpile_cutoff_slider != nullptr) { + int32_t index = 0; + + while (index < stockpile_cutoff_slider->get_max_value() && calculate_trade_menu_stockpile_cutoff_amount_fp( + index * stockpile_cutoff_slider->get_step_size() + ) < good_data.stockpile_cutoff) { + ++index; + } + + // TODO - use a more efficient algorithm, e.g. some kind of binary search + + stockpile_cutoff_slider->set_value(index, false); + } ret[trade_detail_slider_amount_key] = good_data.stockpile_cutoff.to_float(); ret[trade_detail_government_needs_key] = good_data.government_needs.to_float(); ret[trade_detail_army_needs_key] = good_data.army_needs.to_float(); @@ -317,3 +330,9 @@ Dictionary MenuSingleton::get_trade_menu_tables_info() const { return ret; } + +float MenuSingleton::calculate_trade_menu_stockpile_cutoff_amount(GUIScrollbar const* slider) { + ERR_FAIL_NULL_V(slider, 0.0f); + + return calculate_trade_menu_stockpile_cutoff_amount_fp(slider->get_value_scaled_fp()); +} diff --git a/game/src/Game/GameSession/NationManagementScreen/TradeMenu.gd b/game/src/Game/GameSession/NationManagementScreen/TradeMenu.gd index 544da242..d12ab21d 100644 --- a/game/src/Game/GameSession/NationManagementScreen/TradeMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/TradeMenu.gd @@ -173,12 +173,19 @@ func _ready() -> void: if _trade_detail_automate_checkbox: _trade_detail_automate_checkbox.set_tooltip_string("AUTOMATE_TRADE_CHECK") _trade_detail_buy_sell_stockpile_checkbox = get_gui_icon_button_from_nodepath(^"./country_trade/trade_details/sell_stockpile") + if _trade_detail_buy_sell_stockpile_checkbox: + _trade_detail_buy_sell_stockpile_checkbox.toggled.connect(_update_trade_order_buy_sell) _trade_detail_buy_sell_stockpile_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/sell_stockpile_label") _trade_detail_stockpile_slider_description_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/sell_slidier_desc") _trade_detail_stockpile_slider_scrollbar = get_gui_scrollbar_from_nodepath(^"./country_trade/trade_details/sell_slider") _trade_detail_stockpile_slider_amount_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/slider_value") if _trade_detail_stockpile_slider_amount_label: _trade_detail_stockpile_slider_amount_label.set_auto_translate(false) + if _trade_detail_stockpile_slider_scrollbar: + _trade_detail_stockpile_slider_scrollbar.value_changed.connect( + func(value : int) -> void: + _update_stockpile_slider_amount_label(MenuSingleton.calculate_trade_menu_stockpile_cutoff_amount(_trade_detail_stockpile_slider_scrollbar)) + ) _trade_detail_confirm_trade_button = get_gui_icon_button_from_nodepath(^"./country_trade/trade_details/confirm_trade") if _trade_detail_confirm_trade_button: _trade_detail_confirm_trade_button.pressed.connect( @@ -256,12 +263,11 @@ func _update_info() -> void: hide() func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: - # If the desired good is already selected, do nothing (current index will never be negative, so -1 forces a refresh) - if _trade_detail_good_index == new_trade_detail_good_index: - return - - # If the new index isn't negative, update the current index to match it (newly selected good) - if new_trade_detail_good_index >= 0: + # If the new index isn't negative, update the current index to match it + # Even if the new index is the same as the current index, it indicates a forced refresh (including the trade order + # buy/sell checkbox and stockpile cutoff slider, which otherwise wouldn't be refreshed) + var force_refresh : bool = new_trade_detail_good_index >= 0 + if force_refresh: _trade_detail_good_index = new_trade_detail_good_index # Trade details @@ -271,7 +277,6 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: const trade_detail_price_history_key : StringName = &"trade_detail_price_history" const trade_detail_is_automated_key : StringName = &"trade_detail_is_automated" const trade_detail_is_selling_key : StringName = &"trade_detail_is_selling" # or buying (false) - const trade_detail_slider_value_key : StringName = &"trade_detail_slider_value" # linear slider value const trade_detail_slider_amount_key : StringName = &"trade_detail_slider_amount" # exponential good amount const trade_detail_government_needs_key : StringName = &"trade_detail_government_needs" const trade_detail_army_needs_key : StringName = &"trade_detail_army_needs" @@ -281,7 +286,9 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: const trade_detail_pop_needs_key : StringName = &"trade_detail_pop_needs" const trade_detail_available_key : StringName = &"trade_detail_available" - var trade_info : Dictionary = MenuSingleton.get_trade_menu_trade_details_info(_trade_detail_good_index) + var trade_info : Dictionary = MenuSingleton.get_trade_menu_trade_details_info( + _trade_detail_good_index, _trade_detail_stockpile_slider_scrollbar if force_refresh else null + ) var trade_detail_good_name : String = trade_info.get(trade_detail_good_name_key, "") @@ -322,28 +329,16 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: _trade_detail_good_chart_time_label.add_substitution("MONTHS", str(price_history.size())) var is_automated : bool = trade_info.get(trade_detail_is_automated_key, false) - var is_selling : bool = trade_info.get(trade_detail_is_selling_key, false) if _trade_detail_automate_checkbox: # Investigate whether set_pressed_no_signal can/should be used here _trade_detail_automate_checkbox.set_pressed(is_automated) - if _trade_detail_buy_sell_stockpile_checkbox: - # Investigate whether set_pressed_no_signal can/should be used here - _trade_detail_buy_sell_stockpile_checkbox.set_pressed(is_selling) - - if _trade_detail_buy_sell_stockpile_label: - _trade_detail_buy_sell_stockpile_label.set_text("SELL" if is_selling else "BUY") - - if _trade_detail_stockpile_slider_description_label: - _trade_detail_stockpile_slider_description_label.set_text("MINIMUM_STOCKPILE_TARGET" if is_selling else "MAXIMUM_STOCKPILE_TARGET") + if force_refresh: + _update_trade_order_buy_sell(trade_info.get(trade_detail_is_selling_key, false)) - if _trade_detail_stockpile_slider_scrollbar: - _trade_detail_stockpile_slider_scrollbar.set_value(trade_info.get(trade_detail_slider_value_key, 0), false) - - if _trade_detail_stockpile_slider_amount_label: - var slider_amount : float = trade_info.get(trade_detail_slider_amount_key, 0) - _trade_detail_stockpile_slider_amount_label.set_text(GUINode.float_to_string_dp(slider_amount, 3 if slider_amount < 10.0 else 2)) + if _trade_detail_stockpile_slider_amount_label: + _update_stockpile_slider_amount_label(trade_info.get(trade_detail_slider_amount_key, 0)) if _trade_detail_confirm_trade_button: _trade_detail_confirm_trade_button.set_disabled(is_automated) @@ -379,6 +374,20 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: if _trade_detail_good_available_label: _trade_detail_good_available_label.add_substitution("VAL", GUINode.float_to_string_dp(trade_info.get(trade_detail_available_key, 0), 2)) +func _update_trade_order_buy_sell(is_selling : bool) -> void: + if _trade_detail_buy_sell_stockpile_checkbox: + # Investigate whether set_pressed_no_signal can/should be used here + _trade_detail_buy_sell_stockpile_checkbox.set_pressed(is_selling) + + if _trade_detail_buy_sell_stockpile_label: + _trade_detail_buy_sell_stockpile_label.set_text("SELL" if is_selling else "BUY") + + if _trade_detail_stockpile_slider_description_label: + _trade_detail_stockpile_slider_description_label.set_text("MINIMUM_STOCKPILE_TARGET" if is_selling else "MAXIMUM_STOCKPILE_TARGET") + +func _update_stockpile_slider_amount_label(slider_amount : float) -> void: + _trade_detail_stockpile_slider_amount_label.set_text(GUINode.float_to_string_dp(slider_amount, 3 if slider_amount < 10.0 else 2)) + func _change_table_sorting(table : Table, column : int) -> void: if _table_sort_columns[table] != column: _table_sort_columns[table] = column