diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation
index 05baea57..91d7b0a9 160000
--- a/extension/deps/openvic-simulation
+++ b/extension/deps/openvic-simulation
@@ -1 +1 @@
-Subproject commit 05baea57e45e8bfc09869f296330908d2269ddb6
+Subproject commit 91d7b0a9c7319a2d144efcec4d846bb5fdaa4c37
diff --git a/extension/doc_classes/GUILineChart.xml b/extension/doc_classes/GUILineChart.xml
index b8e3a34a..26e1eec3 100644
--- a/extension/doc_classes/GUILineChart.xml
+++ b/extension/doc_classes/GUILineChart.xml
@@ -29,6 +29,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/extension/doc_classes/GUINode.xml b/extension/doc_classes/GUINode.xml
index 87af2f88..2e19e0d8 100644
--- a/extension/doc_classes/GUINode.xml
+++ b/extension/doc_classes/GUINode.xml
@@ -55,6 +55,13 @@
+
+
+
+
+
+
+
@@ -67,6 +74,13 @@
+
+
+
+
+
+
+
@@ -79,6 +93,13 @@
+
+
+
+
+
+
+
@@ -91,6 +112,13 @@
+
+
+
+
+
+
+
@@ -103,6 +131,13 @@
+
+
+
+
+
+
+
@@ -115,6 +150,13 @@
+
+
+
+
+
+
+
@@ -127,6 +169,13 @@
+
+
+
+
+
+
+
@@ -139,6 +188,13 @@
+
+
+
+
+
+
+
@@ -151,6 +207,13 @@
+
+
+
+
+
+
+
@@ -170,6 +233,13 @@
+
+
+
+
+
+
+
@@ -182,6 +252,13 @@
+
+
+
+
+
+
+
@@ -194,6 +271,13 @@
+
+
+
+
+
+
+
@@ -206,6 +290,13 @@
+
+
+
+
+
+
+
diff --git a/extension/doc_classes/MenuSingleton.xml b/extension/doc_classes/MenuSingleton.xml
index c4e85637..b96d465d 100644
--- a/extension/doc_classes/MenuSingleton.xml
+++ b/extension/doc_classes/MenuSingleton.xml
@@ -170,6 +170,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -337,6 +353,14 @@
+
+
+
+
+
+
+
+
diff --git a/extension/src/openvic-extension/classes/GUILineChart.cpp b/extension/src/openvic-extension/classes/GUILineChart.cpp
index 31bb6e47..c0b5a020 100644
--- a/extension/src/openvic-extension/classes/GUILineChart.cpp
+++ b/extension/src/openvic-extension/classes/GUILineChart.cpp
@@ -14,6 +14,9 @@ void GUILineChart::_bind_methods() {
OV_BIND_METHOD(GUILineChart::clear);
OV_BIND_METHOD(GUILineChart::clear_lines);
+ OV_BIND_METHOD(GUILineChart::get_min_value);
+ OV_BIND_METHOD(GUILineChart::get_max_value);
+
OV_BIND_METHOD(GUILineChart::set_gfx_line_chart_name, { "new_gfx_line_chart_name" });
OV_BIND_METHOD(GUILineChart::get_gfx_line_chart_name);
@@ -118,6 +121,9 @@ Error GUILineChart::set_gradient_line(PackedFloat32Array const& line_values, flo
}
}
+ min_value = central_value - min_value_range;
+ max_value = central_value + min_value_range;
+
if (min_value_range == 0.0f) {
min_value_range = 1.0f;
} else {
diff --git a/extension/src/openvic-extension/classes/GUILineChart.hpp b/extension/src/openvic-extension/classes/GUILineChart.hpp
index 697679ae..10163d63 100644
--- a/extension/src/openvic-extension/classes/GUILineChart.hpp
+++ b/extension/src/openvic-extension/classes/GUILineChart.hpp
@@ -12,7 +12,8 @@ namespace OpenVic {
GFX::LineChart const* gfx_line_chart = nullptr;
int32_t point_count = 0;
- float min_value = 0.0f, max_value = 0.0f;
+ float PROPERTY(min_value, 0.0f);
+ float PROPERTY(max_value, 0.0f);
protected:
static void _bind_methods();
diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp
index e1e50fb5..63de6afa 100644
--- a/extension/src/openvic-extension/classes/GUINode.cpp
+++ b/extension/src/openvic-extension/classes/GUINode.cpp
@@ -67,6 +67,7 @@ void GUINode::_bind_methods() {
#define GET_BINDINGS(type, name) \
OV_BIND_SMETHOD(get_##name##_from_node, { "node" }); \
+ OV_BIND_SMETHOD(get_##name##_from_node_and_path, { "node", "path" }); \
OV_BIND_METHOD(GUINode::get_##name##_from_nodepath, { "path" });
APPLY_TO_CHILD_TYPES(GET_BINDINGS)
@@ -139,6 +140,9 @@ static T* _cast_node(Node* node) {
type* GUINode::get_##name##_from_node(Node* node) { \
return _cast_node(node); \
} \
+ type* GUINode::get_##name##_from_node_and_path(Node* node, NodePath const& path) { \
+ return _cast_node(node->get_node_internal(path)); \
+ } \
type* GUINode::get_##name##_from_nodepath(NodePath const& path) const { \
return _cast_node(get_node_internal(path)); \
}
diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp
index a701f1ef..538749f9 100644
--- a/extension/src/openvic-extension/classes/GUINode.hpp
+++ b/extension/src/openvic-extension/classes/GUINode.hpp
@@ -66,6 +66,25 @@ namespace OpenVic {
static godot::LineEdit* get_line_edit_from_node(godot::Node* node);
static GUILineChart* get_gui_line_chart_from_node(godot::Node* node);
+ // These expect a non-null node!
+ static GUIIconButton* get_gui_icon_button_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static GUIMaskedFlagButton* get_gui_masked_flag_button_from_node_and_path(
+ godot::Node* node, godot::NodePath const& path
+ );
+ static GUILabel* get_gui_label_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static godot::Panel* get_panel_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static GUIProgressBar* get_gui_progress_bar_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static GUIIcon* get_gui_icon_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static GUIMaskedFlag* get_gui_masked_flag_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static GUIPieChart* get_gui_pie_chart_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static GUIOverlappingElementsBox* get_gui_overlapping_elements_box_from_node_and_path(
+ godot::Node* node, godot::NodePath const& path
+ );
+ static GUIScrollbar* get_gui_scrollbar_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static GUIListBox* get_gui_listbox_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static godot::LineEdit* get_line_edit_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+ static GUILineChart* get_gui_line_chart_from_node_and_path(godot::Node* node, godot::NodePath const& path);
+
GUIIconButton* get_gui_icon_button_from_nodepath(godot::NodePath const& path) const;
GUIMaskedFlagButton* get_gui_masked_flag_button_from_nodepath(godot::NodePath const& path) const;
GUILabel* get_gui_label_from_nodepath(godot::NodePath const& path) const;
diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp
index 1d70b9ab..20460979 100644
--- a/extension/src/openvic-extension/singletons/MenuSingleton.cpp
+++ b/extension/src/openvic-extension/singletons/MenuSingleton.cpp
@@ -404,6 +404,16 @@ void MenuSingleton::_bind_methods() {
BIND_ENUM_CONSTANT(SORT_SIZE_CHANGE);
BIND_ENUM_CONSTANT(SORT_LITERACY);
+ /* 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_tables_info);
+
+ BIND_ENUM_CONSTANT(TRADE_SETTING_NONE);
+ BIND_ENUM_CONSTANT(TRADE_SETTING_AUTOMATED);
+ BIND_ENUM_CONSTANT(TRADE_SETTING_BUYING);
+ BIND_ENUM_CONSTANT(TRADE_SETTING_SELLING);
+
/* MILITARY MENU */
OV_BIND_METHOD(MenuSingleton::get_military_menu_info, {
"leader_sort_key", "sort_leaders_descending",
diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.hpp b/extension/src/openvic-extension/singletons/MenuSingleton.hpp
index fb5b1d4d..2623b2e8 100644
--- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp
+++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp
@@ -96,6 +96,10 @@ namespace OpenVic {
std::vector pops, filtered_pops;
};
+ enum TradeSettingBit {
+ TRADE_SETTING_NONE = 0, TRADE_SETTING_AUTOMATED = 1, TRADE_SETTING_BUYING = 2, TRADE_SETTING_SELLING = 4
+ };
+
enum LeaderSortKey {
LEADER_SORT_NONE, LEADER_SORT_PRESTIGE, LEADER_SORT_TYPE, LEADER_SORT_NAME, LEADER_SORT_ASSIGNMENT,
MAX_LEADER_SORT_KEY
@@ -233,6 +237,11 @@ namespace OpenVic {
/* Array of GFXPieChartTexture::godot_pie_chart_data_t. */
godot::TypedArray get_population_menu_distribution_info() const;
+ /* 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_tables_info() const;
+
/* MILITARY MENU */
godot::Dictionary make_leader_dict(LeaderBase const& leader);
template
@@ -257,5 +266,6 @@ namespace OpenVic {
VARIANT_ENUM_CAST(OpenVic::MenuSingleton::ProvinceListEntry);
VARIANT_ENUM_CAST(OpenVic::MenuSingleton::PopSortKey);
+VARIANT_ENUM_CAST(OpenVic::MenuSingleton::TradeSettingBit);
VARIANT_ENUM_CAST(OpenVic::MenuSingleton::LeaderSortKey);
VARIANT_ENUM_CAST(OpenVic::MenuSingleton::UnitGroupSortKey);
diff --git a/extension/src/openvic-extension/singletons/TradeMenu.cpp b/extension/src/openvic-extension/singletons/TradeMenu.cpp
new file mode 100644
index 00000000..8a65d7fb
--- /dev/null
+++ b/extension/src/openvic-extension/singletons/TradeMenu.cpp
@@ -0,0 +1,323 @@
+#include "MenuSingleton.hpp"
+
+#include
+
+#include "openvic-extension/classes/GUILabel.hpp"
+#include "openvic-extension/singletons/GameSingleton.hpp"
+#include "openvic-extension/utility/Utilities.hpp"
+
+using namespace OpenVic;
+using namespace godot;
+
+/* TRADE MENU */
+
+Dictionary MenuSingleton::get_trade_menu_good_categories_info() const {
+ static const StringName good_index_key = "good_index";
+ static const StringName current_price_key = "current_price";
+ static const StringName price_change_key = "price_change";
+ static const StringName demand_tooltip_key = "demand_tooltip";
+ static const StringName trade_settings_key = "trade_settings";
+
+ GameSingleton const& game_singleton = *GameSingleton::get_singleton();
+ InstanceManager const* instance_manager = game_singleton.get_instance_manager();
+ ERR_FAIL_NULL_V(instance_manager, {});
+
+ GoodInstanceManager const& good_instance_manager = instance_manager->get_good_instance_manager();
+ GoodDefinitionManager const& good_definition_manager = good_instance_manager.get_good_definition_manager();
+
+ CountryInstance const* country = game_singleton.get_viewed_country();
+
+ Dictionary ret;
+
+ for (GoodCategory const& good_category : good_definition_manager.get_good_categories()) {
+ TypedArray array;
+
+ for (GoodDefinition const* good_definition : good_category.get_good_definitions()) {
+ GoodInstance const& good_instance = good_instance_manager.get_good_instance_from_definition(*good_definition);
+
+ if (!good_instance.get_is_available() || !good_definition->get_is_tradeable()) {
+ continue;
+ }
+
+ Dictionary good_dict;
+
+ good_dict[good_index_key] = static_cast(good_definition->get_index());
+ good_dict[current_price_key] = good_instance.get_price().to_float();
+ good_dict[price_change_key] = good_instance.get_price_change_yesterday().to_float();
+
+ {
+ static const StringName in_demand_localisation_key = "TRADE_IN_DEMAND";
+ static const StringName not_in_demand_localisation_key = "TRADE_NOT_IN_DEMAND";
+ static const StringName supply_localisation_key = "SUPPLY";
+ static const StringName demand_localisation_key = "DEMAND";
+ static const StringName actual_bought_localisation_key = "ACTUAL_BOUGHT";
+ static const String val_replace_key = "$VAL$";
+
+ const fixed_point_t supply = good_instance.get_total_supply_yesterday();
+ const fixed_point_t demand = good_instance.get_total_demand_yesterday();
+
+ good_dict[demand_tooltip_key] = tr(
+ demand > supply ? in_demand_localisation_key : not_in_demand_localisation_key
+ ) + get_tooltip_separator() + tr(supply_localisation_key).replace(
+ val_replace_key, Utilities::fixed_point_to_string_dp(supply, 3)
+ ) + "\n" + tr(demand_localisation_key).replace(
+ val_replace_key, Utilities::fixed_point_to_string_dp(demand, 3)
+ ) + "\n" + tr(actual_bought_localisation_key).replace(
+ val_replace_key, Utilities::fixed_point_to_string_dp(good_instance.get_quantity_traded_yesterday(), 3)
+ );
+ }
+
+ if (country != nullptr) {
+ CountryInstance::good_data_t const& good_data = country->get_goods_data()[good_instance];
+
+ // Trade settings:
+ // - 1 bit: automated (1) or not (0)
+ // - 2 bit: buying (1) or not (0)
+ // - 4 bit: selling (1) or not (0)
+ // The automated bit can be 0 or 1, regardless of the buying and selling bits' values.
+ // The buying and selling bits can both be 0 or 1 and 0, but never both 1.
+
+ int32_t trade_settings = TRADE_SETTING_NONE;
+
+ if (good_data.is_selling) {
+ if (good_data.stockpile_amount > good_data.stockpile_cutoff) {
+ trade_settings = TRADE_SETTING_SELLING;
+ }
+ } else {
+ if (good_data.stockpile_amount < good_data.stockpile_cutoff) {
+ trade_settings = TRADE_SETTING_BUYING;
+ }
+ }
+
+ if (good_data.is_automated) {
+ trade_settings |= TRADE_SETTING_AUTOMATED;
+ }
+
+ good_dict[trade_settings_key] = trade_settings;
+ }
+
+ array.push_back(good_dict);
+ }
+
+ ret[Utilities::std_to_godot_string(good_category.get_identifier())] = std::move(array);
+ }
+
+ return ret;
+}
+
+Dictionary MenuSingleton::get_trade_menu_trade_details_info(int32_t trade_detail_good_index) 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";
+ static const StringName trade_detail_navy_needs_key = "trade_detail_navy_needs";
+ static const StringName trade_detail_production_needs_key = "trade_detail_production_needs";
+ static const StringName trade_detail_overseas_needs_key = "trade_detail_overseas_needs";
+ static const StringName trade_detail_factory_needs_key = "trade_detail_factory_needs";
+ static const StringName trade_detail_pop_needs_key = "trade_detail_pop_needs";
+ static const StringName trade_detail_available_key = "trade_detail_available";
+
+ GameSingleton const& game_singleton = *GameSingleton::get_singleton();
+ InstanceManager const* instance_manager = game_singleton.get_instance_manager();
+ ERR_FAIL_NULL_V(instance_manager, {});
+
+ GoodInstance const* good_instance =
+ instance_manager->get_good_instance_manager().get_good_instance_by_index(trade_detail_good_index);
+ ERR_FAIL_NULL_V(good_instance, {});
+
+ CountryInstance const* country = game_singleton.get_viewed_country();
+
+ Dictionary ret;
+
+ ret[trade_detail_good_name_key] = Utilities::std_to_godot_string(good_instance->get_identifier());
+ ret[trade_detail_good_price_key] = good_instance->get_price().to_float();
+ ret[trade_detail_good_base_price_key] = good_instance->get_good_definition().get_base_price().to_float();
+ {
+ ValueHistory const& good_price_history = good_instance->get_price_history();
+
+ PackedFloat32Array price_history;
+
+ if (price_history.resize(good_price_history.size()) == OK) {
+ for (size_t idx = 0; idx < good_price_history.size(); ++idx) {
+ price_history[idx] = good_price_history[idx].to_float();
+ }
+
+ ret[trade_detail_price_history_key] = std::move(price_history);
+ } else {
+ UtilityFunctions::push_error(
+ "Failed to resize price history array to the correct size (",
+ static_cast(good_price_history.size()), ")"
+ );
+ }
+ }
+
+ if (unlikely(country == nullptr)) {
+ return ret;
+ }
+
+ CountryInstance::good_data_t const& good_data = country->get_goods_data()[*good_instance];
+
+ 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();
+ 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();
+ ret[trade_detail_navy_needs_key] = good_data.navy_needs.to_float();
+ ret[trade_detail_production_needs_key] = good_data.production_needs.to_float();
+ ret[trade_detail_overseas_needs_key] = good_data.overseas_needs.to_float();
+ ret[trade_detail_factory_needs_key] = good_data.factory_needs.to_float();
+ ret[trade_detail_pop_needs_key] = good_data.pop_needs.to_float();
+ ret[trade_detail_available_key] = good_data.available_amount.to_float();
+
+ return ret;
+}
+
+Dictionary MenuSingleton::get_trade_menu_tables_info() const {
+ static const StringName good_producers_tooltips_key = "good_producers_tooltips";
+ static const StringName good_trading_yesterday_tooltips_key = "good_trading_yesterday_tooltips";
+ static const StringName government_needs_key = "government_needs";
+ static const StringName factory_needs_key = "factory_needs";
+ static const StringName pop_needs_key = "pop_needs";
+ static const StringName market_activity_key = "market_activity";
+ static const StringName stockpile_key = "stockpile";
+ static const StringName common_market_key = "common_market";
+
+ GameSingleton const& game_singleton = *GameSingleton::get_singleton();
+ InstanceManager const* instance_manager = game_singleton.get_instance_manager();
+ ERR_FAIL_NULL_V(instance_manager, {});
+ GoodInstanceManager const& good_instance_manager = instance_manager->get_good_instance_manager();
+
+ CountryInstance const* country = game_singleton.get_viewed_country();
+
+ Dictionary ret;
+
+ // This needs an entry for every good, even untradeable and unavailable ones, so we can look entries up with good indices
+ PackedStringArray good_producers_tooltips;
+ // TODO - replace test code with actual top producers
+ CountryInstanceManager const& country_instance_manager = instance_manager->get_country_instance_manager();
+ for (GoodInstance const& good : good_instance_manager.get_good_instances()) {
+ static const StringName top_producers_localisation_key = "TRADE_TOP_PRODUCERS";
+
+ String tooltip = tr(Utilities::std_to_godot_string(good.get_identifier())) + get_tooltip_separator() +
+ tr(top_producers_localisation_key);
+
+ for (size_t index = 0; index < 5; ++index) {
+ CountryInstance const* country = country_instance_manager.get_country_instance_by_index(index + 1);
+
+ ERR_CONTINUE(country == nullptr);
+
+ static const String top_producer_template_string = "\n" + GUILabel::get_flag_marker() + "%s %s: %s";
+
+ tooltip += vformat(
+ top_producer_template_string,
+ Utilities::std_to_godot_string(country->get_identifier()),
+ _get_country_name(*country),
+ Utilities::fixed_point_to_string_dp(fixed_point_t::parse(1000) / static_cast(index + 1), 2)
+ );
+ }
+
+ good_producers_tooltips.push_back(tooltip);
+ }
+ ret[good_producers_tooltips_key] = std::move(good_producers_tooltips);
+
+ if (unlikely(country == nullptr)) {
+ return ret;
+ }
+
+ PackedStringArray good_trading_yesterday_tooltips;
+ PackedVector2Array government_needs;
+ PackedVector2Array factory_needs;
+ PackedVector2Array pop_needs;
+ PackedVector3Array market_activity;
+ PackedVector3Array stockpile;
+ PackedVector4Array common_market;
+
+ for (auto const& [good, good_data] : country->get_goods_data()) {
+ if (!good.get_is_available() || !good.get_good_definition().get_is_tradeable()) {
+ continue;
+ }
+
+ static const StringName stockpile_bought_localisation_key = "TRADE_STOCKPILE_BUY";
+ static const StringName stockpile_sold_localisation_key = "TRADE_STOCKPILE_SOLD";
+ static const String money_replace_key = "$MONEY$";
+ static const String units_replace_key = "$UNITS$";
+
+ fixed_point_t stockpile_change_yesterday = good_data.stockpile_change_yesterday;
+ String tooltip;
+
+ if (stockpile_change_yesterday > 0) {
+ tooltip = tr(stockpile_bought_localisation_key);
+ } else {
+ tooltip = tr(stockpile_sold_localisation_key);
+ stockpile_change_yesterday = -stockpile_change_yesterday;
+ }
+
+ good_trading_yesterday_tooltips.push_back(
+ tooltip.replace(
+ money_replace_key, Utilities::fixed_point_to_string_dp(stockpile_change_yesterday, 2)
+ ).replace(
+ units_replace_key, Utilities::fixed_point_to_string_dp(stockpile_change_yesterday * good.get_price(), 2)
+ )
+ );
+
+ const float good_index = good.get_good_definition().get_index();
+
+ if (good_data.government_needs != fixed_point_t::_0()) {
+ government_needs.push_back({
+ good_index,
+ good_data.government_needs.to_float()
+ });
+ }
+
+ if (good_data.factory_needs != fixed_point_t::_0()) {
+ factory_needs.push_back({
+ good_index,
+ good_data.factory_needs.to_float()
+ });
+ }
+
+ if (good_data.pop_needs != fixed_point_t::_0()) {
+ pop_needs.push_back({
+ good_index, good_data.pop_needs.to_float()
+ });
+ }
+
+ market_activity.push_back({
+ good_index,
+ good_data.exported_amount.abs().to_float(),
+ (good_data.exported_amount * good.get_price()).to_float()
+ });
+
+ stockpile.push_back({
+ good_index,
+ good_data.stockpile_amount.to_float(),
+ good_data.stockpile_change_yesterday.to_float()
+ });
+
+ // TODO - replace with actual common market data
+ common_market.push_back({
+ good_index,
+ good_index * 100,
+ -good_index,
+ good_index * 10
+ });
+ }
+
+ ret[good_trading_yesterday_tooltips_key] = std::move(good_trading_yesterday_tooltips);
+ ret[government_needs_key] = std::move(government_needs);
+ ret[factory_needs_key] = std::move(factory_needs);
+ ret[pop_needs_key] = std::move(pop_needs);
+ ret[market_activity_key] = std::move(market_activity);
+ ret[stockpile_key] = std::move(stockpile);
+ ret[common_market_key] = std::move(common_market);
+
+ return ret;
+}
diff --git a/game/src/Game/GameSession/NationManagementScreen/TradeMenu.gd b/game/src/Game/GameSession/NationManagementScreen/TradeMenu.gd
index 34931ba4..a5aa33cc 100644
--- a/game/src/Game/GameSession/NationManagementScreen/TradeMenu.gd
+++ b/game/src/Game/GameSession/NationManagementScreen/TradeMenu.gd
@@ -4,12 +4,72 @@ var _active : bool = false
const _screen : NationManagement.Screen = NationManagement.Screen.TRADE
+const _gui_file : String = "country_trade"
+
+var _trade_detail_good_index : int = 0
+
+# Trade details
+var _trade_detail_good_icon : GUIIcon
+var _trade_detail_good_name_label : GUILabel
+var _trade_detail_good_price_label : GUILabel
+var _trade_detail_good_price_line_chart : GUILineChart
+var _trade_detail_good_price_low_label : GUILabel
+var _trade_detail_good_price_high_label : GUILabel
+var _trade_detail_good_chart_time_label : GUILabel
+var _trade_detail_automate_checkbox : GUIIconButton
+var _trade_detail_buy_sell_stockpile_checkbox : GUIIconButton
+var _trade_detail_buy_sell_stockpile_label : GUILabel
+var _trade_detail_stockpile_slider_description_label : GUILabel
+var _trade_detail_stockpile_slider_scrollbar : GUIScrollbar
+var _trade_detail_stockpile_slider_amount_label : GUILabel
+var _trade_detail_confirm_trade_button : GUIIconButton
+var _trade_detail_government_good_needs_label : GUILabel
+var _trade_detail_factory_good_needs_label : GUILabel
+var _trade_detail_pop_good_needs_label : GUILabel
+var _trade_detail_good_available_label : GUILabel
+
+# Goods tables
+enum Table {
+ GOVERNMENT_NEEDS,
+ FACTORY_NEEDS,
+ POP_NEEDS,
+ MARKET_ACTIVITY,
+ STOCKPILE,
+ COMMON_MARKET
+}
+const TABLE_NAMES : PackedStringArray = [
+ "government_needs", "factory_needs", "pop_needs", "market_activity", "stockpile", "common_market"
+]
+const TABLE_ENTRY_NAMES : PackedStringArray = [
+ "goods_needs_entry", "goods_needs_entry", "goods_needs_entry", "market_activity_entry", "stockpile_entry", "common_market_entry"
+]
+# Nested Array contains only NodePaths
+const TABLE_ITEM_PATHS : Array[Array] = [
+ [^"./goods_type", ^"./value"], [^"./goods_type", ^"./value"], [^"./goods_type", ^"./value"],
+ [^"./goods_type", ^"./activity", ^"./cost"],
+ [^"./goods_type", ^"./value", ^"./change"],
+ [^"./goods_type", ^"./total", ^"./produce_change", ^"./exported"]
+]
+
+var _table_listboxes : Array[GUIListBox]
+
+const TABLE_UNSORTED : int = 255
+const TABLE_COLUMN_KEYS : Array[StringName] = [&"COLUMN_0", &"COLUMN_1", &"COLUMN_2", &"COLUMN_3"]
+var _table_sort_columns : PackedByteArray
+
+const SORT_DESCENDING : int = 0
+const SORT_ASCENDING : int = 1
+var _table_sort_directions : PackedByteArray
+
+# Good entries
+var _goods_entry_offset : Vector2
+
func _ready() -> void:
GameSingleton.gamestate_updated.connect(_update_info)
Events.NationManagementScreens.update_active_nation_management_screen.connect(_on_update_active_nation_management_screen)
- add_gui_element("country_trade", "country_trade")
+ add_gui_element(_gui_file, "country_trade")
set_click_mask_from_nodepaths([^"./country_trade/main_bg"])
@@ -17,21 +77,141 @@ func _ready() -> void:
if close_button:
close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen))
- var good_price_line_chart : GUILineChart = get_gui_line_chart_from_nodepath(^"./country_trade/trade_details/price_linechart")
-
- if good_price_line_chart:
- # TEST COLOURED LINES
- var colours : PackedColorArray = [
- Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA,
- Color.CYAN, Color.ORANGE, Color.CRIMSON, Color.FOREST_GREEN
- ]
- for n : int in colours.size():
- const point_count : int = 36
- var values : PackedFloat32Array
- for x : int in point_count:
- values.push_back(1000 * sin((float(x) / (point_count - 1) + float(n) / (colours.size() * 2)) * 4 * PI) + 4000)
- good_price_line_chart.add_coloured_line(values, colours[n])
- good_price_line_chart.scale_coloured_lines()
+ # Goods tables
+ _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/government_needs_list"))
+ _table_sort_columns.push_back(TABLE_UNSORTED)
+ _table_sort_directions.push_back(SORT_DESCENDING)
+ var sort_government_needs_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_government_sort_by_goods")
+ if sort_government_needs_by_good_button:
+ sort_government_needs_by_good_button.pressed.connect(_change_table_sorting.bind(Table.GOVERNMENT_NEEDS, 0))
+ var sort_government_needs_by_need_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_government_sort_by_value")
+ if sort_government_needs_by_need_button:
+ sort_government_needs_by_need_button.pressed.connect(_change_table_sorting.bind(Table.GOVERNMENT_NEEDS, 1))
+
+ _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/factory_needs_list"))
+ _table_sort_columns.push_back(TABLE_UNSORTED)
+ _table_sort_directions.push_back(SORT_DESCENDING)
+ var sort_factory_needs_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_factories_sort_by_goods")
+ if sort_factory_needs_by_good_button:
+ sort_factory_needs_by_good_button.pressed.connect(_change_table_sorting.bind(Table.FACTORY_NEEDS, 0))
+ var sort_factory_needs_by_need_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_factories_sort_by_value")
+ if sort_factory_needs_by_need_button:
+ sort_factory_needs_by_need_button.pressed.connect(_change_table_sorting.bind(Table.FACTORY_NEEDS, 1))
+
+ _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/pop_needs_list"))
+ _table_sort_columns.push_back(TABLE_UNSORTED)
+ _table_sort_directions.push_back(SORT_DESCENDING)
+ var sort_pop_needs_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_pops_sort_by_goods")
+ if sort_pop_needs_by_good_button:
+ sort_pop_needs_by_good_button.pressed.connect(_change_table_sorting.bind(Table.POP_NEEDS, 0))
+ var sort_pop_needs_by_need_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_pops_sort_by_value")
+ if sort_pop_needs_by_need_button:
+ sort_pop_needs_by_need_button.pressed.connect(_change_table_sorting.bind(Table.POP_NEEDS, 1))
+
+ _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/market_activity_list"))
+ _table_sort_columns.push_back(TABLE_UNSORTED)
+ _table_sort_directions.push_back(SORT_DESCENDING)
+ var sort_market_activity_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_goods")
+ if sort_market_activity_by_good_button:
+ sort_market_activity_by_good_button.pressed.connect(_change_table_sorting.bind(Table.MARKET_ACTIVITY, 0))
+ var sort_market_activity_by_activity_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_activity")
+ if sort_market_activity_by_activity_button:
+ sort_market_activity_by_activity_button.pressed.connect(_change_table_sorting.bind(Table.MARKET_ACTIVITY, 1))
+ var sort_market_activity_by_cost_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_cost")
+ if sort_market_activity_by_cost_button:
+ sort_market_activity_by_cost_button.pressed.connect(_change_table_sorting.bind(Table.MARKET_ACTIVITY, 2))
+
+ _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/stockpile_list"))
+ _table_sort_columns.push_back(TABLE_UNSORTED)
+ _table_sort_directions.push_back(SORT_DESCENDING)
+ var sort_stockpile_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_goods")
+ if sort_stockpile_by_good_button:
+ sort_stockpile_by_good_button.pressed.connect(_change_table_sorting.bind(Table.STOCKPILE, 0))
+ var sort_stockpile_by_stock_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_value")
+ if sort_stockpile_by_stock_button:
+ sort_stockpile_by_stock_button.pressed.connect(_change_table_sorting.bind(Table.STOCKPILE, 1))
+ var sort_stockpile_by_increase_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_change")
+ if sort_stockpile_by_increase_button:
+ sort_stockpile_by_increase_button.pressed.connect(_change_table_sorting.bind(Table.STOCKPILE, 2))
+
+ _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/common_market_list"))
+ _table_sort_columns.push_back(TABLE_UNSORTED)
+ _table_sort_directions.push_back(SORT_DESCENDING)
+ var sort_common_market_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_goods")
+ if sort_common_market_by_good_button:
+ sort_common_market_by_good_button.pressed.connect(_change_table_sorting.bind(Table.COMMON_MARKET, 0))
+ var sort_common_market_by_available_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_produced")
+ if sort_common_market_by_available_button:
+ sort_common_market_by_available_button.pressed.connect(_change_table_sorting.bind(Table.COMMON_MARKET, 1))
+ var sort_common_market_by_increase_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_diff")
+ if sort_common_market_by_increase_button:
+ sort_common_market_by_increase_button.pressed.connect(_change_table_sorting.bind(Table.COMMON_MARKET, 2))
+ var sort_common_market_by_exported_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_exported")
+ if sort_common_market_by_exported_button:
+ sort_common_market_by_exported_button.pressed.connect(_change_table_sorting.bind(Table.COMMON_MARKET, 3))
+
+ # Trade details
+ _trade_detail_good_icon = get_gui_icon_from_nodepath(^"./country_trade/trade_details/goods_icon")
+ _trade_detail_good_name_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/goods_title")
+ _trade_detail_good_price_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/goods_price")
+ if _trade_detail_good_price_label:
+ _trade_detail_good_price_label.set_auto_translate(false)
+ _trade_detail_good_price_line_chart = get_gui_line_chart_from_nodepath(^"./country_trade/trade_details/price_linechart")
+ _trade_detail_good_price_low_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/price_chart_low")
+ if _trade_detail_good_price_low_label:
+ _trade_detail_good_price_low_label.set_auto_translate(false)
+ _trade_detail_good_price_high_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/price_chart_high")
+ if _trade_detail_good_price_high_label:
+ _trade_detail_good_price_high_label.set_auto_translate(false)
+ _trade_detail_good_chart_time_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/price_chart_time")
+ if _trade_detail_good_chart_time_label:
+ _trade_detail_good_chart_time_label.set_text("PRICE_HISTORY_TIME_RANGE")
+ var trade_detail_automate_label : GUILabel = get_gui_label_from_nodepath(^"./country_trade/trade_details/automate_label")
+ if trade_detail_automate_label:
+ trade_detail_automate_label.set_tooltip_string("AUTOMATE_TRADE_CHECK")
+ _trade_detail_automate_checkbox = get_gui_icon_button_from_nodepath(^"./country_trade/trade_details/automate")
+ 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")
+ _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)
+ _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(
+ func() -> void:
+ # TODO - implement button functionality
+ print("Confirm trade!")
+ )
+ var good_details_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/trade_details/goods_details")
+ if good_details_button:
+ good_details_button.pressed.connect(
+ func() -> void:
+ # TODO - open trade details menu
+ print("Open details menu for good index ", _trade_detail_good_index)
+ )
+
+ _trade_detail_government_good_needs_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/goods_need_gov_desc")
+ if _trade_detail_government_good_needs_label:
+ _trade_detail_government_good_needs_label.set_text("GOV_NEED_DETAIL")
+ _trade_detail_factory_good_needs_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/goods_need_factory_desc")
+ if _trade_detail_factory_good_needs_label:
+ _trade_detail_factory_good_needs_label.set_text("FACTORY_NEED_DETAIL")
+ _trade_detail_pop_good_needs_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/produced_detail_desc") # names switched in GUI file
+ if _trade_detail_pop_good_needs_label:
+ _trade_detail_pop_good_needs_label.set_text("POP_NEED_DETAIL")
+ _trade_detail_good_available_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/goods_need_pop_desc") # names switched in GUI file
+ if _trade_detail_good_available_label:
+ _trade_detail_good_available_label.set_text("PRODUCED_DETAIL_REMOVE")
+
+ # Good entries
+ _goods_entry_offset = get_gui_position(_gui_file, "goods_entry_offset")
+ if _goods_entry_offset.x == 0.0:
+ push_error("TradeMenu: Goods entry x offset is zero, setting to 1 to avoid divide by 0 errors")
+ _goods_entry_offset.x = 1.0
_update_info()
@@ -46,7 +226,380 @@ func _on_update_active_nation_management_screen(active_screen : NationManagement
func _update_info() -> void:
if _active:
- # TODO - update UI state
+
+ _update_trade_details()
+
+ const good_producers_tooltips_key : StringName = &"good_producers_tooltips"
+ const good_trading_yesterday_tooltips_key : StringName = &"good_trading_yesterday_tooltips"
+ const government_needs_key : StringName = &"government_needs"
+ const factory_needs_key : StringName = &"factory_needs"
+ const pop_needs_key : StringName = &"pop_needs"
+ const market_activity_key : StringName = &"market_activity"
+ const stockpile_key : StringName = &"stockpile"
+ const common_market_key : StringName = &"common_market"
+
+ var trade_info : Dictionary = MenuSingleton.get_trade_menu_tables_info()
+
+ var good_producers_tooltips : PackedStringArray = trade_info.get(good_producers_tooltips_key, [] as PackedStringArray)
+
+ _generate_listbox(Table.GOVERNMENT_NEEDS, trade_info.get(government_needs_key, [] as PackedVector2Array), good_producers_tooltips)
+ _generate_listbox(Table.FACTORY_NEEDS, trade_info.get(factory_needs_key, [] as PackedVector2Array), good_producers_tooltips)
+ _generate_listbox(Table.POP_NEEDS, trade_info.get(pop_needs_key, [] as PackedVector2Array), good_producers_tooltips)
+ _generate_listbox(Table.MARKET_ACTIVITY, trade_info.get(market_activity_key, [] as PackedVector3Array), good_producers_tooltips)
+ _generate_listbox(Table.STOCKPILE, trade_info.get(stockpile_key, [] as PackedVector3Array), trade_info.get(good_trading_yesterday_tooltips_key, [] as PackedStringArray))
+ _generate_listbox(Table.COMMON_MARKET, trade_info.get(common_market_key, [] as PackedVector4Array), good_producers_tooltips)
+
+ _generate_good_entries(good_producers_tooltips)
+
show()
else:
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:
+ _trade_detail_good_index = new_trade_detail_good_index
+
+ # Trade details
+ const trade_detail_good_name_key : StringName = &"trade_detail_good_name"
+ const trade_detail_good_price_key : StringName = &"trade_detail_good_price"
+ const trade_detail_good_base_price_key : StringName = &"trade_detail_good_base_price"
+ 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"
+ const trade_detail_navy_needs_key : StringName = &"trade_detail_navy_needs"
+ const trade_detail_production_needs_key : StringName = &"trade_detail_production_needs"
+ const trade_detail_overseas_needs_key : StringName = &"trade_detail_overseas_needs"
+ const trade_detail_factory_needs_key : StringName = &"trade_detail_factory_needs"
+ 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_detail_good_name : String = trade_info.get(trade_detail_good_name_key, "")
+
+ if _trade_detail_good_icon:
+ _trade_detail_good_icon.set_icon_index(_trade_detail_good_index + 2)
+ _trade_detail_good_icon.set_tooltip_string(trade_detail_good_name)
+
+ if _trade_detail_good_name_label:
+ _trade_detail_good_name_label.set_text(trade_detail_good_name)
+
+ if _trade_detail_good_price_label:
+ _trade_detail_good_price_label.set_text(GUINode.float_to_string_dp(trade_info.get(trade_detail_good_price_key, 0), 3) + "¤")
+
+ var price_history : PackedFloat32Array = trade_info.get(trade_detail_price_history_key, [] as PackedFloat32Array)
+ if price_history.size() == 1:
+ # We cannot draw a line with just one point
+ push_error("TradeMenu: Price history has only one point: ", price_history)
+ price_history.clear()
+ var base_price : float = trade_info.get(trade_detail_good_base_price_key, 0)
+ var price_low : float = base_price
+ var price_high : float = base_price
+
+ if _trade_detail_good_price_line_chart:
+ if price_history.is_empty():
+ _trade_detail_good_price_line_chart.clear_lines()
+ else:
+ _trade_detail_good_price_line_chart.set_gradient_line(price_history, base_price, 1.0)
+ price_low = _trade_detail_good_price_line_chart.get_min_value()
+ price_high = _trade_detail_good_price_line_chart.get_max_value()
+
+ if _trade_detail_good_price_low_label:
+ _trade_detail_good_price_low_label.set_text(GUINode.float_to_string_dp(price_low, 1) + "¤")
+
+ if _trade_detail_good_price_high_label:
+ _trade_detail_good_price_high_label.set_text(GUINode.float_to_string_dp(price_high, 1) + "¤")
+
+ if _trade_detail_good_chart_time_label:
+ _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 _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_confirm_trade_button:
+ _trade_detail_confirm_trade_button.set_disabled(is_automated)
+ _trade_detail_confirm_trade_button.set_tooltip_string("TRADE_DISABLED_AUTOMATE" if is_automated else "TRADE_CONFIRM_DESC")
+
+ if _trade_detail_government_good_needs_label:
+ _trade_detail_government_good_needs_label.add_substitution("VAL", GUINode.float_to_string_dp(trade_info.get(trade_detail_government_needs_key, 0), 2))
+ var government_needs_tooltip : String
+ var army_needs : float = trade_info.get(trade_detail_army_needs_key, 0)
+ if army_needs > 0:
+ government_needs_tooltip = tr(&"TRADE_SUPPLY_NEED_A").replace("$VAL$", GUINode.float_to_string_dp(army_needs, 2))
+ var navy_needs : float = trade_info.get(trade_detail_navy_needs_key, 0)
+ if navy_needs > 0:
+ government_needs_tooltip += tr(&"TRADE_SUPPLY_NEED_N").replace("$VAL$", GUINode.float_to_string_dp(navy_needs, 2))
+ var production_needs : float = trade_info.get(trade_detail_production_needs_key, 0)
+ if production_needs > 0:
+ government_needs_tooltip += tr(&"TRADE_TEMP_PROD_NEED").replace("$VAL$", GUINode.float_to_string_dp(production_needs, 2))
+ var overseas_needs : float = trade_info.get(trade_detail_overseas_needs_key, 0)
+ if overseas_needs > 0:
+ government_needs_tooltip += tr(&"TRADE_OVERSEAS_NEED").replace("$VAL$", GUINode.float_to_string_dp(overseas_needs, 2))
+ if not government_needs_tooltip.is_empty():
+ government_needs_tooltip = government_needs_tooltip.trim_suffix("\n")
+ _trade_detail_government_good_needs_label.set_tooltip_string(government_needs_tooltip)
+
+ if _trade_detail_factory_good_needs_label:
+ _trade_detail_factory_good_needs_label.add_substitution("VAL", GUINode.float_to_string_dp(trade_info.get(trade_detail_factory_needs_key, 0), 2))
+
+ if _trade_detail_pop_good_needs_label:
+ _trade_detail_pop_good_needs_label.add_substitution("VAL", GUINode.float_to_string_dp(trade_info.get(trade_detail_pop_needs_key, 0), 2))
+
+ 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 _change_table_sorting(table : Table, column : int) -> void:
+ if _table_sort_columns[table] != column:
+ _table_sort_columns[table] = column
+ _table_sort_directions[table] = SORT_DESCENDING
+ else:
+ _table_sort_directions[table] = SORT_ASCENDING if _table_sort_directions[table] == SORT_DESCENDING else SORT_DESCENDING
+
+ print("Sorting table ", TABLE_NAMES[table], " by column ", column, " ", "ascending" if _table_sort_directions[table] == SORT_ASCENDING else "descending")
+
+ _sort_table(table)
+
+func _sort_table(table : Table) -> void:
+ var column : int = _table_sort_columns[table]
+ if column == TABLE_UNSORTED:
+ return
+
+ var listbox : GUIListBox = _table_listboxes[table]
+ var sort_key : StringName = TABLE_COLUMN_KEYS[column]
+ var descending : bool = _table_sort_directions[table] == SORT_DESCENDING
+
+ var items : Array[Node] = listbox.get_children()
+
+ for child : Node in items:
+ listbox.remove_child(child)
+
+ items.sort_custom(
+ (func(a : Node, b : Node) -> bool: return a.get_meta(sort_key) > b.get_meta(sort_key))
+ if descending else
+ (func(a : Node, b : Node) -> bool: return a.get_meta(sort_key) < b.get_meta(sort_key))
+ )
+
+ for child : Node in items:
+ listbox.add_child(child)
+
+func _float_to_string_suffixed_dp(value : float, decimals : int) -> String:
+ if value < 1000:
+ return GUINode.float_to_string_dp(value, decimals)
+ else:
+ return GUINode.float_to_string_dp(value / 1000, decimals) + "k"
+
+func _float_to_string_suffixed_dp_dynamic(value : float) -> String:
+ if value < 2:
+ return GUINode.float_to_string_dp(value, 3)
+ elif value < 100:
+ return GUINode.float_to_string_dp(value, 2)
+ else:
+ return _float_to_string_suffixed_dp(value, 1)
+
+# data is either a PackedVector2Array, PackedVector3Array or PackedVector4Array
+func _generate_listbox(table : Table, data : Variant, good_tooltips : PackedStringArray) -> void:
+ var listbox : GUIListBox = _table_listboxes[table]
+ if not listbox:
+ return
+
+ var entry_name : String = TABLE_ENTRY_NAMES[table]
+
+ listbox.clear_children(data.size())
+ while listbox.get_child_count() < data.size():
+ var entry : Panel = GUINode.generate_gui_element(_gui_file, entry_name)
+ if not entry:
+ break
+ listbox.add_child(entry)
+
+ var item_paths : Array = TABLE_ITEM_PATHS[table]
+
+ for index in min(listbox.get_child_count(), data.size()):
+ var entry : Panel = listbox.get_child(index)
+ if not entry:
+ break
+
+ # entry_data is either a Vector2, Vector3 or Vector4
+ var entry_data : Variant = data[index]
+ var good_index : int = int(entry_data.x)
+ var good_tooltip : String = good_tooltips[good_index] if good_index < good_tooltips.size() else ""
+
+ var good_button : GUIIconButton = GUINode.get_gui_icon_button_from_node_and_path(entry, item_paths[0])
+ if good_button:
+ good_button.set_icon_index(good_index + 2)
+ good_button.set_tooltip_string(good_tooltip)
+ good_button.pressed.connect(
+ func() -> void:
+ _update_trade_details(entry.get_meta(TABLE_COLUMN_KEYS[0]))
+ )
+ entry.set_meta(TABLE_COLUMN_KEYS[0], good_index)
+
+ var set_tooltips : bool = table == Table.STOCKPILE
+
+ var second_column : GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[1])
+ if second_column:
+ second_column.set_auto_translate(false)
+ second_column.set_text(
+ _float_to_string_suffixed_dp_dynamic(entry_data.y)
+ if table <= Table.POP_NEEDS
+ else _float_to_string_suffixed_dp(abs(entry_data.y), 1)
+ if table == Table.MARKET_ACTIVITY
+ else _float_to_string_suffixed_dp(entry_data.y, 2)
+ )
+ if set_tooltips:
+ second_column.set_tooltip_string(good_tooltip)
+ entry.set_meta(TABLE_COLUMN_KEYS[1], entry_data.y)
+
+ if item_paths.size() < 3:
+ continue
+
+ var third_column : GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[2])
+ if third_column:
+ third_column.set_auto_translate(false)
+ third_column.set_text(
+ _float_to_string_suffixed_dp(entry_data.z, 0)
+ if table == Table.MARKET_ACTIVITY
+ else ("+" if entry_data.z >= 0 else "") + GUINode.float_to_string_dp(entry_data.z, 2)
+ if table == Table.STOCKPILE
+ else "(%s)" % GUINode.float_to_string_dp(entry_data.z, 0)
+ )
+ if set_tooltips:
+ third_column.set_tooltip_string(good_tooltip)
+ entry.set_meta(TABLE_COLUMN_KEYS[2], entry_data.z)
+
+ if item_paths.size() < 4:
+ continue
+
+ var fourth_column : GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[3])
+ if fourth_column:
+ fourth_column.set_auto_translate(false)
+ fourth_column.set_text(_float_to_string_suffixed_dp(entry_data.w, 1))
+ if set_tooltips:
+ fourth_column.set_tooltip_string(good_tooltip)
+ entry.set_meta(TABLE_COLUMN_KEYS[3], entry_data.w)
+
+ _sort_table(table)
+
+func _generate_good_entries(good_tooltips : PackedStringArray) -> void:
+ var good_categories_info : Dictionary = MenuSingleton.get_trade_menu_good_categories_info()
+
+ for good_category : String in good_categories_info:
+ var good_category_panel : Panel = get_panel_from_nodepath("./country_trade/group_%s" % good_category)
+ if not good_category_panel:
+ continue
+
+ # Fall back to 1 if panel width is too small to avoid division by zero
+ var max_items_per_row : int = max(floor(good_category_panel.get_size().x / _goods_entry_offset.x), 1)
+
+ var good_category_goods : Array[Dictionary] = good_categories_info[good_category]
+
+ var child_count : int = good_category_panel.get_child_count()
+
+ while child_count < good_category_goods.size():
+ var good_entry_panel : Panel = generate_gui_element(_gui_file, "goods_entry")
+ if not good_entry_panel:
+ break
+ good_entry_panel.set_position(
+ _goods_entry_offset * Vector2(child_count % max_items_per_row, child_count / max_items_per_row)
+ )
+ good_category_panel.add_child(good_entry_panel)
+ child_count += 1
+
+ while child_count > good_category_goods.size():
+ child_count -= 1
+ good_category_panel.remove_child(good_category_panel.get_child(child_count))
+
+ for category_index : int in min(child_count, good_category_goods.size()):
+ var good_entry_panel : Panel = GUINode.get_panel_from_node(good_category_panel.get_child(category_index))
+ if not good_entry_panel:
+ continue
+
+ const good_index_key : StringName = &"good_index"
+ const current_price_key : StringName = &"current_price"
+ const price_change_key : StringName = &"price_change"
+ const demand_tooltip_key : StringName = &"demand_tooltip"
+ const trade_settings_key : StringName = &"trade_settings"
+
+ var good_dict : Dictionary = good_category_goods[category_index]
+
+ var good_index : int = good_dict.get(good_index_key, 0)
+
+ var entry_button : GUIIconButton = get_gui_icon_button_from_node_and_path(good_entry_panel, ^"./entry_button")
+ if entry_button:
+ # Connecting this way ensures the Callable is always the same, preventing errors when good_index changes,
+ # while still allowing updates by changing the meta value rather than the Callable
+ entry_button.pressed.connect(
+ func() -> void:
+ _update_trade_details(good_entry_panel.get_meta(TABLE_COLUMN_KEYS[0]))
+ )
+ good_entry_panel.set_meta(TABLE_COLUMN_KEYS[0], good_index)
+ entry_button.set_tooltip_string(good_dict.get(demand_tooltip_key, ""))
+
+ var good_button : GUIIconButton = get_gui_icon_button_from_node_and_path(good_entry_panel, ^"./goods_type")
+ if good_button:
+ good_button.set_icon_index(good_index + 2)
+ good_button.set_tooltip_string(good_tooltips[good_index] if good_index < good_tooltips.size() else "")
+ good_button.pressed.connect(func() -> void: print("Good button pressed with index ", good_index))
+
+ var price_label : GUILabel = get_gui_label_from_node_and_path(good_entry_panel, ^"./price")
+ if price_label:
+ # TODO - change colour of text if price is very high or very low!!!
+ price_label.set_auto_translate(false)
+ price_label.set_text(GUINode.float_to_string_dp(good_dict.get(current_price_key, 0), 1) + "¤")
+
+ var trend_icon : GUIIcon = get_gui_icon_from_node_and_path(good_entry_panel, ^"./trend_indicator")
+ if trend_icon:
+ var price_change : float = good_dict.get(price_change_key, 0)
+ trend_icon.set_icon_index(1 if price_change > 0 else 3 if price_change < 0 else 2)
+ trend_icon.set_tooltip_string(
+ tr(&"TRADE_PRICE_TREND").replace("$VALUE$", GUINode.float_to_string_dp(price_change, 4))
+ if price_change != 0.0 else "TRADE_PRICE_TREND_UNCHANGED"
+ )
+
+ var trade_settings : int = good_dict.get(trade_settings_key, 0)
+
+ var automated_icon : GUIIcon = get_gui_icon_from_node_and_path(good_entry_panel, ^"./automation_indicator")
+ if automated_icon:
+ automated_icon.set_visible(trade_settings & MenuSingleton.TradeSettingBit.TRADE_SETTING_AUTOMATED)
+
+ var buy_sell_icon : GUIIcon = get_gui_icon_from_node_and_path(good_entry_panel, ^"./selling_indicator")
+ if buy_sell_icon:
+ if trade_settings & MenuSingleton.TradeSettingBit.TRADE_SETTING_BUYING:
+ buy_sell_icon.set_icon_index(2)
+ buy_sell_icon.set_tooltip_string("TRADE_BUYING")
+ buy_sell_icon.show()
+ elif trade_settings & MenuSingleton.TradeSettingBit.TRADE_SETTING_SELLING:
+ buy_sell_icon.set_icon_index(3)
+ buy_sell_icon.set_tooltip_string("TRADE_SELLING")
+ buy_sell_icon.show()
+ else:
+ buy_sell_icon.hide()