From 252307c7ccfa86e2e66bed6b22f7b2d30cc953e0 Mon Sep 17 00:00:00 2001 From: hop311 Date: Sun, 26 Jan 2025 22:37:52 +0000 Subject: [PATCH] Add military menu control script + better modifier tooltip functions --- extension/doc_classes/AssetManager.xml | 12 + extension/doc_classes/GUINode.xml | 7 + extension/doc_classes/MenuSingleton.xml | 31 + .../src/openvic-extension/classes/GUINode.cpp | 13 +- .../src/openvic-extension/classes/GUINode.hpp | 5 +- .../classes/GUIScrollbar.cpp | 2 +- .../singletons/AssetManager.cpp | 14 + .../singletons/AssetManager.hpp | 5 + .../singletons/MenuSingleton.cpp | 1045 ++++++++++++++++- .../singletons/MenuSingleton.hpp | 47 +- .../singletons/PopulationMenu.cpp | 4 +- .../openvic-extension/utility/Utilities.cpp | 47 +- .../openvic-extension/utility/Utilities.hpp | 6 + .../NationManagementScreen/MilitaryMenu.gd | 672 ++++++++++- game/src/Game/GameSession/Topbar.gd | 20 +- 15 files changed, 1840 insertions(+), 90 deletions(-) diff --git a/extension/doc_classes/AssetManager.xml b/extension/doc_classes/AssetManager.xml index 3b7f9838..238065d1 100644 --- a/extension/doc_classes/AssetManager.xml +++ b/extension/doc_classes/AssetManager.xml @@ -7,6 +7,12 @@ + + + + + + @@ -20,6 +26,12 @@ + + + + + + diff --git a/extension/doc_classes/GUINode.xml b/extension/doc_classes/GUINode.xml index 5d3e0ea3..ea1c44ed 100644 --- a/extension/doc_classes/GUINode.xml +++ b/extension/doc_classes/GUINode.xml @@ -37,6 +37,7 @@ + @@ -223,6 +224,12 @@ + + + + + + diff --git a/extension/doc_classes/MenuSingleton.xml b/extension/doc_classes/MenuSingleton.xml index 3144fb4b..c4e85637 100644 --- a/extension/doc_classes/MenuSingleton.xml +++ b/extension/doc_classes/MenuSingleton.xml @@ -55,6 +55,17 @@ + + + + + + + + + + + @@ -326,5 +337,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp index 8105f0b3..89d2a110 100644 --- a/extension/src/openvic-extension/classes/GUINode.cpp +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -82,10 +82,11 @@ void GUINode::_bind_methods() { OV_BIND_METHOD(GUINode::remove_nodes, { "paths" }); OV_BIND_SMETHOD(int_to_string_suffixed, { "val" }); + OV_BIND_SMETHOD(int_to_string_commas, { "val" }); OV_BIND_SMETHOD(float_to_string_suffixed, { "val" }); OV_BIND_SMETHOD(float_to_string_dp, { "val", "decimal_places" }); OV_BIND_SMETHOD(float_to_string_dp_dynamic, { "val" }); - OV_BIND_SMETHOD(format_province_name, { "province_identifier" }); + OV_BIND_SMETHOD(format_province_name, { "province_identifier", "ignore_empty" }, DEFVAL(false)); } GUINode::GUINode() { @@ -233,6 +234,10 @@ String GUINode::int_to_string_suffixed(int64_t val) { return Utilities::int_to_string_suffixed(val); } +String GUINode::int_to_string_commas(int64_t val) { + return Utilities::int_to_string_commas(val); +} + String GUINode::float_to_string_suffixed(float val) { return Utilities::float_to_string_suffixed(val); } @@ -245,13 +250,15 @@ String GUINode::float_to_string_dp_dynamic(float val) { return Utilities::float_to_string_dp_dynamic(val); } -String GUINode::format_province_name(String const& province_identifier) { +String GUINode::format_province_name(String const& province_identifier, bool ignore_empty) { if (!province_identifier.is_empty()) { static const String province_prefix = "PROV"; return province_prefix + province_identifier; - } else { + } else if (!ignore_empty) { static const String no_province = "NO PROVINCE"; return no_province; + } else { + return {}; } } diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp index 453263ad..1f94a8f1 100644 --- a/extension/src/openvic-extension/classes/GUINode.hpp +++ b/extension/src/openvic-extension/classes/GUINode.hpp @@ -88,11 +88,14 @@ namespace OpenVic { godot::Error remove_nodes(godot::TypedArray const& paths) const; static godot::String int_to_string_suffixed(int64_t val); + static godot::String int_to_string_commas(int64_t val); static godot::String float_to_string_suffixed(float val); static godot::String float_to_string_dp(float val, int32_t decimal_places); // 3dp if abs(val) < 2 else 2dp if abs(val) < 10 else 1dp static godot::String float_to_string_dp_dynamic(float val); - static godot::String format_province_name(godot::String const& province_identifier); + // The "ignore_empty" argument refers to what this function produces when given an empty string - if the argument + // is false then empty inputs are replaced with "NO PROVINCE", otherwise they return the empty string unchanged. + static godot::String format_province_name(godot::String const& province_identifier, bool ignore_empty = false); godot::Ref get_click_mask() const; void set_click_mask(godot::Ref const& mask); diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.cpp b/extension/src/openvic-extension/classes/GUIScrollbar.cpp index bc782938..f8d2e7b1 100644 --- a/extension/src/openvic-extension/classes/GUIScrollbar.cpp +++ b/extension/src/openvic-extension/classes/GUIScrollbar.cpp @@ -429,7 +429,7 @@ Error GUIScrollbar::set_gui_scrollbar(GUI::Scrollbar const* new_gui_scrollbar) { fixed_point_t step_size = gui_scrollbar->get_step_size(); if (step_size <= 0) { UtilityFunctions::push_error( - "Invalid step size ", Utilities::std_to_godot_string(step_size.to_string()), " for GUIScrollbar ", + "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; diff --git a/extension/src/openvic-extension/singletons/AssetManager.cpp b/extension/src/openvic-extension/singletons/AssetManager.cpp index 6b7a4f1e..dcb5771e 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.cpp +++ b/extension/src/openvic-extension/singletons/AssetManager.cpp @@ -14,6 +14,8 @@ void AssetManager::_bind_methods() { OV_BIND_METHOD(AssetManager::get_image, { "path", "load_flags" }, DEFVAL(LOAD_FLAG_CACHE_IMAGE)); OV_BIND_METHOD(AssetManager::get_texture, { "path", "load_flags" }, DEFVAL(LOAD_FLAG_CACHE_TEXTURE)); OV_BIND_METHOD(AssetManager::get_font, { "name" }); + OV_BIND_METHOD(AssetManager::get_currency_texture, { "height" }); + OV_BIND_METHOD(AssetManager::get_leader_texture, { "name" }); BIND_ENUM_CONSTANT(LOAD_FLAG_NONE); BIND_ENUM_CONSTANT(LOAD_FLAG_CACHE_IMAGE); @@ -195,6 +197,8 @@ Error AssetManager::preload_textures() { static const String currency_sprite_medium = "GFX_tooltip_money_small"; static const String currency_sprite_small = "GFX_tooltip_money"; + static const String missing_leader_sprite = "GFX_leader_generic0"; + constexpr auto load = [](String const& sprite_name, Ref& texture) -> bool { GFX::Sprite const* sprite = UITools::get_gfx_sprite(sprite_name); ERR_FAIL_NULL_V(sprite, false); @@ -214,6 +218,8 @@ Error AssetManager::preload_textures() { ret &= load(currency_sprite_medium, currency_texture_medium); ret &= load(currency_sprite_small, currency_texture_small); + ret &= load(missing_leader_sprite, missing_leader_texture); + return ERR(ret); } @@ -228,3 +234,11 @@ Ref AssetManager::get_currency_texture(real_t height) const { return currency_texture_small; } } + +Ref AssetManager::get_leader_texture_std(std::string_view name) { + return get_texture(Utilities::std_to_godot_string(CultureManager::make_leader_picture_path(name))); +} + +Ref AssetManager::get_leader_texture(String const& name) { + return get_leader_texture_std(Utilities::godot_to_std_string(name)); +} diff --git a/extension/src/openvic-extension/singletons/AssetManager.hpp b/extension/src/openvic-extension/singletons/AssetManager.hpp index 768aca9a..f68aa55c 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.hpp +++ b/extension/src/openvic-extension/singletons/AssetManager.hpp @@ -83,11 +83,16 @@ namespace OpenVic { godot::Ref PROPERTY(currency_texture_medium); // 24x24 godot::Ref PROPERTY(currency_texture_small); // 16x16 + godot::Ref PROPERTY(missing_leader_texture); + public: godot::Error preload_textures(); /* Get the largest currency texture with height less than the specified font height. */ godot::Ref get_currency_texture(real_t height) const; + + godot::Ref get_leader_texture_std(std::string_view name); + godot::Ref get_leader_texture(godot::String const& name); }; } diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp index 6ae94508..d157d6b1 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.cpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.cpp @@ -9,6 +9,7 @@ #include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/classes/GUINode.hpp" +#include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" #include "openvic-extension/utility/Utilities.hpp" @@ -38,7 +39,7 @@ StringName const& MenuSingleton::_signal_update_tooltip() { return signal_update_tooltip; } -String MenuSingleton::get_state_name(State const& state) const { +String MenuSingleton::_get_state_name(State const& state) const { StateSet const& state_set = state.get_state_set(); const String region_identifier = Utilities::std_to_godot_string(state_set.get_region().get_identifier()); @@ -69,14 +70,14 @@ String MenuSingleton::get_state_name(State const& state) const { if (owned && split) { // COUNTRY STATE/CAPITAL - return get_country_adjective(*state.get_owner()) + " " + name; + return _get_country_adjective(*state.get_owner()) + " " + name; } // STATE/CAPITAL return name; } -String MenuSingleton::get_country_name(CountryInstance const& country) const { +String MenuSingleton::_get_country_name(CountryInstance const& country) const { if (country.get_government_type() != nullptr && !country.get_government_type()->get_identifier().empty()) { const String government_name_key = Utilities::std_to_godot_string(StringUtils::append_string_views( country.get_identifier(), "_", country.get_government_type()->get_identifier() @@ -92,7 +93,7 @@ String MenuSingleton::get_country_name(CountryInstance const& country) const { return tr(Utilities::std_to_godot_string(country.get_identifier())); } -String MenuSingleton::get_country_adjective(CountryInstance const& country) const { +String MenuSingleton::_get_country_adjective(CountryInstance const& country) const { static constexpr std::string_view adjective = "_ADJ"; if (country.get_government_type() != nullptr && !country.get_government_type()->get_identifier().empty()) { @@ -110,58 +111,124 @@ String MenuSingleton::get_country_adjective(CountryInstance const& country) cons return tr(Utilities::std_to_godot_string(StringUtils::append_string_views(country.get_identifier(), adjective))); } -String MenuSingleton::make_modifier_effects_tooltip(ModifierValue const& modifier) const { - if (modifier.empty()) { - return {}; +static constexpr int32_t DECIMAL_PLACES = 2; + +static String _make_modifier_effect_value( + ModifierEffect const& format_effect, fixed_point_t value, bool plus_for_non_negative +) { + String result; + + if (plus_for_non_negative && value >= 0) { + result = "+"; + } + + using enum ModifierEffect::format_t; + + switch (format_effect.get_format()) { + case PROPORTION_DECIMAL: + value *= 100; + [[fallthrough]]; + case PERCENTAGE_DECIMAL: + result += Utilities::fixed_point_to_string_dp(value, DECIMAL_PLACES) + "%"; + break; + case INT: + // This won't produce a decimal point for actual whole numbers, but if the value has a fractional part it will be + // displayed to 2 decimal places. This mirrors the base game, where effects which are meant to have integer values + // will still display a fractional part if they are given one in the game defines. + result += Utilities::fixed_point_to_string_dp(value, value.is_integer() ? -1 : DECIMAL_PLACES); + break; + case RAW_DECIMAL: [[fallthrough]]; + default: // Use raw decimal as fallback format + result += Utilities::fixed_point_to_string_dp(value, DECIMAL_PLACES); + break; + } + + return result; +} + +static String _make_modifier_effect_value_coloured( + ModifierEffect const& format_effect, fixed_point_t value, bool plus_for_non_negative +) { + String result = GUILabel::get_colour_marker(); + + if (value == 0) { + result += "Y"; + } else if (format_effect.is_positive_good() == (value > 0)) { + result += "G"; + } else { + result += "R"; } + result += _make_modifier_effect_value(format_effect, value, plus_for_non_negative); + + static const String end_text = GUILabel::get_colour_marker() + String { "!" }; + result += end_text; + + return result; +} + +String MenuSingleton::_make_modifier_effects_tooltip(ModifierValue const& modifier) const { String result; for (auto const& [effect, value] : modifier.get_values()) { - static const String post_name_text = ": " + GUILabel::get_colour_marker(); + if (value != fixed_point_t::_0()) { + result += "\n" + tr(Utilities::std_to_godot_string(effect->get_localisation_key())) + ": " + + _make_modifier_effect_value_coloured(*effect, value, true); + } + } - result += "\n" + tr(Utilities::std_to_godot_string(effect->get_localisation_key())) + post_name_text; + return result; +} - if (value == 0) { - result += "Y"; - } else if (effect->is_positive_good() == (value > 0)) { - result += "G"; - } else { - result += "R"; - } +template +requires std::same_as || std::same_as +String MenuSingleton::_make_modifier_effect_contributions_tooltip( + T const& modifier_sum, ModifierEffect const& effect, fixed_point_t* tech_contributions, + fixed_point_t* other_contributions, String const& prefix +) const { + String result; - if (value >= 0) { - result += "+"; - } + modifier_sum.for_each_contributing_modifier( + effect, + [this, &effect, tech_contributions, other_contributions, &prefix, &result]( + modifier_entry_t const& modifier_entry, fixed_point_t value + ) -> void { + using enum Modifier::modifier_type_t; + + // TODO - make sure we only include invention contributions from inside their "effect = { ... }" blocks, + // as contributions from outside the blocks are treated as if they're from normal country modifiers and + // displayed as "[invention name]: X.Y%" (both types of contributions can come from the same invention) + if (tech_contributions != nullptr && ( + modifier_entry.modifier.get_type() == TECHNOLOGY || modifier_entry.modifier.get_type() == INVENTION + )) { + *tech_contributions += value; + return; + } - static constexpr int32_t DECIMAL_PLACES = 2; + if (other_contributions != nullptr) { + *other_contributions += value; + } - using enum ModifierEffect::format_t; + result += prefix; - switch (effect->get_format()) { - case PROPORTION_DECIMAL: - result += GUINode::float_to_string_dp((value * 100).to_float(), DECIMAL_PLACES) + "%"; - break; - case PERCENTAGE_DECIMAL: - result += GUINode::float_to_string_dp(value.to_float(), DECIMAL_PLACES) + "%"; - break; - case INT: - result += String::num_int64(value.to_int64_t()); - break; - case RAW_DECIMAL: [[fallthrough]]; - default: // Use raw decimal as fallback format - result += GUINode::float_to_string_dp(value.to_float(), DECIMAL_PLACES); - break; - } + if (effect.is_global()) { + ProvinceInstance const* province = modifier_entry.get_source_province(); + if (province != nullptr) { + result += tr(GUINode::format_province_name(Utilities::std_to_godot_string(province->get_identifier()))); + result += ": "; + } + } - static const String end_text = GUILabel::get_colour_marker() + String { "!" }; - result += end_text; - } + result += tr(Utilities::std_to_godot_string(modifier_entry.modifier.get_identifier())); + result += ": "; + result += _make_modifier_effect_value_coloured(effect, value, true); + } + ); return result; } -String MenuSingleton::make_rules_tooltip(RuleSet const& rules) const { +String MenuSingleton::_make_rules_tooltip(RuleSet const& rules) const { if (rules.empty()) { return {}; } @@ -187,6 +254,57 @@ String MenuSingleton::make_rules_tooltip(RuleSet const& rules) const { return result; } +String MenuSingleton::_make_mobilisation_impact_tooltip() const { + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, {}); + + CountryInstance const* country = game_singleton->get_viewed_country(); + + if (country == nullptr) { + return {}; + } + + IssueManager const& issue_manager = game_singleton->get_definition_manager().get_politics_manager().get_issue_manager(); + + static const StringName mobilisation_impact_tooltip_localisation_key = "MOBILIZATION_IMPACT_LIMIT_DESC"; + static const String mobilisation_impact_tooltip_replace_impact_key = "$IMPACT$"; + static const String mobilisation_impact_tooltip_replace_policy_key = "$POLICY$"; + static const String mobilisation_impact_tooltip_replace_units_key = "$UNITS$"; + + static const StringName mobilisation_impact_tooltip2_localisation_key = "MOBILIZATION_IMPACT_LIMIT_DESC2"; + static const String mobilisation_impact_tooltip2_replace_curr_key = "$CURR$"; + static const String mobilisation_impact_tooltip2_replace_impact_key = "$IMPACT$"; + + static const StringName no_issue = "noIssue"; + + IssueGroup const* war_policy_issue_group = issue_manager.get_issue_group_by_identifier("war_policy"); + Issue const* war_policy_issue = + war_policy_issue_group != nullptr ? country->get_ruling_party()->get_policies()[*war_policy_issue_group] : nullptr; + + const String impact_string = Utilities::fixed_point_to_string_dp(country->get_mobilisation_impact() * 100, 1) + "%"; + + return tr( + mobilisation_impact_tooltip_localisation_key + ).replace( + mobilisation_impact_tooltip_replace_impact_key, impact_string + ).replace( + mobilisation_impact_tooltip_replace_policy_key, tr( + war_policy_issue != nullptr + ? StringName { Utilities::std_to_godot_string(war_policy_issue->get_identifier()) } + : no_issue + ) + ).replace( + mobilisation_impact_tooltip_replace_units_key, + String::num_uint64(country->get_mobilisation_max_regiment_count()) + ) + "\n" + tr( + mobilisation_impact_tooltip2_localisation_key + ).replace( + mobilisation_impact_tooltip2_replace_curr_key, String::num_uint64(country->get_regiment_count()) + ).replace( + mobilisation_impact_tooltip2_replace_impact_key, impact_string + ); +} + void MenuSingleton::_bind_methods() { OV_BIND_SMETHOD(get_tooltip_separator); OV_BIND_METHOD(MenuSingleton::get_country_name_from_identifier, { "country_identifier" }); @@ -278,6 +396,25 @@ void MenuSingleton::_bind_methods() { BIND_ENUM_CONSTANT(SORT_SIZE_CHANGE); BIND_ENUM_CONSTANT(SORT_LITERACY); + /* MILITARY MENU */ + OV_BIND_METHOD(MenuSingleton::get_military_menu_info, { + "leader_sort_key", "sort_leaders_descending", + "army_sort_key", "sort_armies_descending", + "navy_sort_key", "sort_navies_descending" + }); + + BIND_ENUM_CONSTANT(LEADER_SORT_NONE); + BIND_ENUM_CONSTANT(LEADER_SORT_PRESTIGE); + BIND_ENUM_CONSTANT(LEADER_SORT_TYPE); + BIND_ENUM_CONSTANT(LEADER_SORT_NAME); + BIND_ENUM_CONSTANT(LEADER_SORT_ASSIGNMENT); + BIND_ENUM_CONSTANT(MAX_LEADER_SORT_KEY); + + BIND_ENUM_CONSTANT(UNIT_GROUP_SORT_NONE); + BIND_ENUM_CONSTANT(UNIT_GROUP_SORT_NAME); + BIND_ENUM_CONSTANT(UNIT_GROUP_SORT_STRENGTH); + BIND_ENUM_CONSTANT(MAX_UNIT_GROUP_SORT_KEY); + /* Find/Search Panel */ OV_BIND_METHOD(MenuSingleton::generate_search_cache); OV_BIND_METHOD(MenuSingleton::update_search_results, { "text" }); @@ -326,7 +463,7 @@ String MenuSingleton::get_country_name_from_identifier(String const& country_ide ); ERR_FAIL_NULL_V(country, {}); - return get_country_name(*country); + return _get_country_name(*country); } String MenuSingleton::get_country_adjective_from_identifier(String const& country_identifier) const { @@ -345,7 +482,7 @@ String MenuSingleton::get_country_adjective_from_identifier(String const& countr ); ERR_FAIL_NULL_V(country, {}); - return get_country_adjective(*country); + return _get_country_adjective(*country); } /* TOOLTIP */ @@ -395,8 +532,8 @@ static TypedArray _make_buildings_dict_array( Dictionary building_dict; building_dict[building_info_level_key] = static_cast(building.get_level()); building_dict[building_info_expansion_state_key] = static_cast(building.get_expansion_state()); - building_dict[building_info_start_date_key] = Utilities::std_to_godot_string(building.get_start_date().to_string()); - building_dict[building_info_end_date_key] = Utilities::std_to_godot_string(building.get_end_date().to_string()); + building_dict[building_info_start_date_key] = Utilities::date_to_string(building.get_start_date()); + building_dict[building_info_end_date_key] = Utilities::date_to_string(building.get_end_date()); building_dict[building_info_expansion_progress_key] = building.get_expansion_progress(); buildings_array[idx] = std::move(building_dict); @@ -450,7 +587,7 @@ Dictionary MenuSingleton::get_province_info_from_index(int32_t index) const { State const* state = province->get_state(); if (state != nullptr) { - ret[province_info_state_key] = get_state_name(*state); + ret[province_info_state_key] = _get_state_name(*state); } ret[province_info_slave_status_key] = province->get_slave(); @@ -676,7 +813,7 @@ Dictionary MenuSingleton::get_topbar_info() const { // Pair: State name / Power std::vector> industrial_power_states; for (auto const& [state, power] : country->get_industrial_power_from_states()) { - industrial_power_states.emplace_back(get_state_name(*state), power); + industrial_power_states.emplace_back(_get_state_name(*state), power); } std::sort( industrial_power_states.begin(), industrial_power_states.end(), @@ -694,7 +831,7 @@ Dictionary MenuSingleton::get_topbar_info() const { std::vector> industrial_power_from_investments; for (auto const& [country, power] : country->get_industrial_power_from_investments()) { industrial_power_from_investments.emplace_back( - Utilities::std_to_godot_string(country->get_identifier()), get_country_name(*country), power + Utilities::std_to_godot_string(country->get_identifier()), _get_country_name(*country), power ); } std::sort( @@ -770,20 +907,15 @@ Dictionary MenuSingleton::get_topbar_info() const { ret[regiment_count_key] = static_cast(country->get_regiment_count()); ret[max_supported_regiments_key] = static_cast(country->get_max_supported_regiment_count()); - static const StringName mobilised_key = "mobilised"; + static const StringName is_mobilised_key = "is_mobilised"; static const StringName mobilisation_regiments_key = "mobilisation_regiments"; - static const StringName mobilisation_impact_key = "mobilisation_impact"; - static const StringName war_policy_key = "war_policy"; - static const StringName mobilisation_max_regiments_key = "mobilisation_max_regiments"; + static const StringName mobilisation_impact_tooltip_key = "mobilisation_impact_tooltip"; if (country->is_mobilised()) { - ret[mobilised_key] = true; + ret[is_mobilised_key] = true; } else { - ret[mobilised_key] = false; ret[mobilisation_regiments_key] = static_cast(country->get_mobilisation_potential_regiment_count()); - ret[mobilisation_impact_key] = country->get_mobilisation_impact().to_float(); - ret[war_policy_key] = String {}; // TODO - get ruling party's war policy - ret[mobilisation_max_regiments_key] = static_cast(country->get_mobilisation_max_regiment_count()); + ret[mobilisation_impact_tooltip_key] = _make_mobilisation_impact_tooltip(); } return ret; @@ -872,6 +1004,803 @@ String MenuSingleton::get_longform_date() const { return Utilities::date_to_formatted_string(instance_manager->get_today(), true); } +/* MILITARY MENU */ + +static Ref _get_leader_picture(LeaderBase const& leader) { + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, {}); + + if (!leader.get_picture().empty()) { + const Ref texture = asset_manager->get_leader_texture_std(leader.get_picture()); + + if (texture.is_valid()) { + return texture; + } + } + + return asset_manager->get_missing_leader_texture(); +} + +Dictionary MenuSingleton::make_leader_dict(LeaderBase const& leader) { + const decltype(cached_leader_dicts)::const_iterator it = cached_leader_dicts.find(&leader); + + if (it != cached_leader_dicts.end()) { + return it->second; + } + + static const StringName military_info_leader_name_key = "leader_name"; + static const StringName military_info_leader_picture_key = "leader_picture"; + static const StringName military_info_leader_prestige_key = "leader_prestige"; + static const StringName military_info_leader_prestige_tooltip_key = "leader_prestige_tooltip"; + static const StringName military_info_leader_background_key = "leader_background"; + static const StringName military_info_leader_personality_key = "leader_personality"; + static const StringName military_info_leader_can_be_used_key = "leader_can_be_used"; + static const StringName military_info_leader_assignment_key = "leader_assignment"; + static const StringName military_info_leader_location_key = "leader_location"; + static const StringName military_info_leader_tooltip_key = "leader_tooltip"; + + Dictionary leader_dict; + String tooltip; + ModifierValue modifier_value; + + // Picture + leader_dict[military_info_leader_picture_key] = _get_leader_picture(leader); + + { + // Branched (can be used, assignment, location, title) + static const auto branched_section = []( + LeaderBranched const& leader, Dictionary& leader_dict + ) -> void { + leader_dict[military_info_leader_can_be_used_key] = leader.get_can_be_used(); + + UnitInstanceGroup const* group = leader.get_unit_instance_group(); + if (group != nullptr) { + leader_dict[military_info_leader_assignment_key] = Utilities::std_to_godot_string(group->get_name()); + + ProvinceInstance const* location = group->get_position(); + if (location != nullptr) { + leader_dict[military_info_leader_location_key] = + Utilities::std_to_godot_string(location->get_identifier()); + } + } + }; + + using enum UnitType::branch_t; + + switch (leader.get_branch()) { + case LAND: { + static const StringName general_localisation_key = "MILITARY_GENERAL_TOOLTIP"; + tooltip = tr(general_localisation_key) + " "; + + branched_section(static_cast(leader), leader_dict); + } break; + + case NAVAL: { + static const StringName admiral_localisation_key = "MILITARY_ADMIRAL_TOOLTIP"; + tooltip = tr(admiral_localisation_key) + " "; + + branched_section(static_cast(leader), leader_dict); + } break; + + default: + UtilityFunctions::push_error( + "Invalid branch type \"", static_cast(leader.get_branch()), "\" for leader \"", + Utilities::std_to_godot_string(leader.get_name()), "\"" + ); + } + } + + { + // Name + String leader_name = Utilities::std_to_godot_string(leader.get_name()); + + // Make yellow then revert back to default (white) + static const String leader_name_prefix = GUILabel::get_colour_marker() + String { "Y" }; + static const String leader_name_suffix = GUILabel::get_colour_marker() + String { "!" }; + + tooltip += leader_name_prefix + leader_name + leader_name_suffix; + + leader_dict[military_info_leader_name_key] = std::move(leader_name); + } + + { + // Prestige + const fixed_point_t prestige = leader.get_prestige(); + fixed_point_t morale_bonus, organisation_bonus; + + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + if (game_singleton != nullptr) { + DefinitionManager const& definition_manager = game_singleton->get_definition_manager(); + modifier_value = + definition_manager.get_military_manager().get_leader_trait_manager().get_leader_prestige_modifier() * prestige; + + ModifierEffectCache const& modifier_effect_cache = + definition_manager.get_modifier_manager().get_modifier_effect_cache(); + + morale_bonus = modifier_value.get_effect(*modifier_effect_cache.get_morale_leader()); + organisation_bonus = modifier_value.get_effect(*modifier_effect_cache.get_organisation()); + } + + static const StringName prestige_localisation_key = "PRESTIGE_SCORE"; + static const String value_replace_key = "$VAL$"; + + String prestige_tooltip = tr(prestige_localisation_key).replace( + value_replace_key, Utilities::float_to_string_dp(prestige * 100, 2) + "%" + ); + + tooltip += "\n" + prestige_tooltip; + + static const StringName morale_localisation_key = "PRESTIGE_MORALE_BONUS"; + static const StringName organisation_localisation_key = "PRESTIGE_MAX_ORG_BONUS"; + + // Morale and organisation bonuses are always green with a + sign, matching the base game's behaviour + static const String value_prefix = GUILabel::get_colour_marker() + String { "G+" }; + + prestige_tooltip += "\n" + tr(morale_localisation_key).replace( + value_replace_key, value_prefix + Utilities::float_to_string_dp(morale_bonus * 100, 2) + "%" + ) + "\n" + tr(organisation_localisation_key).replace( + value_replace_key, value_prefix + Utilities::float_to_string_dp(organisation_bonus * 100, 2) + "%" + ); + + leader_dict[military_info_leader_prestige_key] = prestige.to_float(); + leader_dict[military_info_leader_prestige_tooltip_key] = std::move(prestige_tooltip); + } + + { + // Background + String background; + + if (leader.get_background() != nullptr) { + background = tr(Utilities::std_to_godot_string(leader.get_background()->get_identifier())); + modifier_value += *leader.get_background(); + } else { + static const StringName missing_background = "no_background"; + background = tr(missing_background); + } + + static const StringName background_localisation_key = "MILITARY_BACKGROUND"; + static const String background_replace_key = "$NAME$"; + + tooltip += "\n" + tr(background_localisation_key).replace( + background_replace_key, background + ); + + leader_dict[military_info_leader_background_key] = std::move(background); + } + + { + // Personality + String personality; + + if (leader.get_personality() != nullptr) { + personality = tr(Utilities::std_to_godot_string(leader.get_personality()->get_identifier())); + modifier_value += *leader.get_personality(); + } else { + static const StringName missing_personality = "no_personality"; + personality = tr(missing_personality); + } + + static const StringName personality_localisation_key = "MILITARY_PERSONALITY"; + static const String personality_replace_key = "$NAME$"; + + tooltip += "\n" + tr(personality_localisation_key).replace( + personality_replace_key, personality + ); + + leader_dict[military_info_leader_personality_key] = std::move(personality); + } + + tooltip += _make_modifier_effects_tooltip(modifier_value); + + leader_dict[military_info_leader_tooltip_key] = std::move(tooltip); + + cached_leader_dicts.emplace(&leader, leader_dict); + + return leader_dict; +} + +template +Dictionary MenuSingleton::make_unit_group_dict(UnitInstanceGroup const& unit_group) { + static const StringName military_info_unit_group_leader_picture_key = "unit_group_leader_picture"; + static const StringName military_info_unit_group_leader_tooltip_key = "unit_group_leader_tooltip"; + static const StringName military_info_unit_group_name_key = "unit_group_name"; + static const StringName military_info_unit_group_location_key = "unit_group_location"; + static const StringName military_info_unit_group_unit_count_key = "unit_group_unit_count"; + static const StringName military_info_unit_group_men_count_key = "unit_group_men_count"; // armies only + static const StringName military_info_unit_group_max_men_count_key = "unit_group_max_men_count"; // armies only + static const StringName military_info_unit_group_morale_key = "unit_group_morale"; + static const StringName military_info_unit_group_strength_key = "unit_group_strength"; + static const StringName military_info_unit_group_moving_tooltip_key = "unit_group_moving_tooltip"; + static const StringName military_info_unit_group_digin_tooltip_key = "unit_group_digin_tooltip"; // armies only + static const StringName military_info_unit_group_combat_key = "unit_group_combat"; + + using enum UnitType::branch_t; + + Dictionary unit_group_dict; + + if (unit_group.get_leader() != nullptr) { + static const StringName military_info_leader_picture_key = "leader_picture"; + static const StringName military_info_leader_tooltip_key = "leader_tooltip"; + + const Dictionary leader_dict = make_leader_dict(*unit_group.get_leader()); + + unit_group_dict[military_info_unit_group_leader_picture_key] = + leader_dict.get(military_info_leader_picture_key, Ref {}); + unit_group_dict[military_info_unit_group_leader_tooltip_key] = + leader_dict.get(military_info_leader_tooltip_key, String {}); + } + + unit_group_dict[military_info_unit_group_name_key] = Utilities::std_to_godot_string(unit_group.get_name()); + if (unit_group.get_position() != nullptr) { + unit_group_dict[military_info_unit_group_location_key] = + Utilities::std_to_godot_string(unit_group.get_position()->get_identifier()); + } + unit_group_dict[military_info_unit_group_unit_count_key] = static_cast(unit_group.get_unit_count()); + + if constexpr (Branch == LAND) { + // TODO - calculate (max) men and morale properly in ArmyInstance and RegimentInstances and set here + unit_group_dict[military_info_unit_group_men_count_key] = static_cast(unit_group.get_unit_count() * 3000); + unit_group_dict[military_info_unit_group_max_men_count_key] = + static_cast(unit_group.get_unit_count() * 3000); + } + unit_group_dict[military_info_unit_group_morale_key] = 1.0f; + unit_group_dict[military_info_unit_group_strength_key] = 1.0f; + + // TODO - check if moving, and if true get current movement info + if (true /* unit_group.is_moving() */) { + static const StringName moving_localisation_key = "MILITARY_MOVING_TOOLTIP"; + static const String moving_location_replace_key = "$LOCATION$"; + static const String moving_date_replace_key = "$DATE$"; + + ProvinceInstance const* destination = nullptr; + Date arrival_date {}; + + unit_group_dict[military_info_unit_group_moving_tooltip_key] = tr(moving_localisation_key).replace( + moving_location_replace_key, + tr(GUINode::format_province_name( + destination != nullptr ? Utilities::std_to_godot_string(destination->get_identifier()) : String {}, false + )) + ).replace(moving_date_replace_key, Utilities::date_to_string(arrival_date)); + } + + if constexpr (Branch == LAND) { + // TODO - check if digging in, and if true get days spent digging in + if (true /* unit_group.is_digging_in() */) { + static const StringName digin_localisation_key = "MILITARY_DIGIN_TOOLTIP"; + static const String moving_days_replace_key = "$DAYS$"; + + int64_t days_spent_digging_in = 4; + + unit_group_dict[military_info_unit_group_digin_tooltip_key] = tr(digin_localisation_key).replace( + moving_days_replace_key, String::num_int64(days_spent_digging_in) + ); + } + } + + // TODO - check if in combat + if (true /* unit_group.is_in_combat() */) { + unit_group_dict[military_info_unit_group_combat_key] = true; + } + + return unit_group_dict; +} + +Dictionary MenuSingleton::make_in_progress_unit_dict() const { + static const StringName military_info_unit_progress_key = "unit_progress"; + static const StringName military_info_unit_icon_key = "unit_icon"; + static const StringName military_info_unit_name_key = "unit_name"; + static const StringName military_info_unit_location_key = "unit_location"; + static const StringName military_info_unit_eta_key = "unit_eta"; + static const StringName military_info_unit_tooltip_key = "unit_tooltip"; + + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + DefinitionManager const& definition_manager = game_singleton->get_definition_manager(); + GoodDefinitionManager const& good_definition_manager = + definition_manager.get_economy_manager().get_good_definition_manager(); + + // TODO - remove test data, read actual in-progress units from SIM + UnitType const* unit_type = definition_manager.get_military_manager().get_unit_type_manager().get_unit_type_by_index(0); + ProvinceInstance const* location = game_singleton->get_viewed_country()->get_capital(); + const Date eta { 1900 }; + const fixed_point_t progress = fixed_point_t::_0_50(); + const ordered_map> required_goods { + { + good_definition_manager.get_good_definition_by_index(0), + { fixed_point_t::parse(1234) / 100, fixed_point_t::parse(1900) / 100 } + }, { + good_definition_manager.get_good_definition_by_index(1), + { fixed_point_t::parse(888) / 100, fixed_point_t::parse(1444) / 100 } + }, { + good_definition_manager.get_good_definition_by_index(2), + { fixed_point_t::parse(1622) / 100, fixed_point_t::parse(1622) / 100 } + }, { + good_definition_manager.get_good_definition_by_index(3), + { fixed_point_t::parse(211) / 100, fixed_point_t::parse(805) / 100 } + } + }; + + Dictionary in_progress_unit_dict; + + in_progress_unit_dict[military_info_unit_progress_key] = progress.to_float(); + in_progress_unit_dict[military_info_unit_icon_key] = unit_type->get_icon(); + in_progress_unit_dict[military_info_unit_name_key] = Utilities::std_to_godot_string(unit_type->get_identifier()); + in_progress_unit_dict[military_info_unit_location_key] = Utilities::std_to_godot_string(location->get_identifier()); + in_progress_unit_dict[military_info_unit_eta_key] = Utilities::date_to_string(eta); + + String tooltip; + + for (auto const& [good, required_amounts] : required_goods) { + if (required_amounts.first < required_amounts.second) { + tooltip += "\n" + tr(Utilities::std_to_godot_string(good->get_identifier())) + " - " + + Utilities::fixed_point_to_string_dp(required_amounts.first, 2) + "/" + + Utilities::fixed_point_to_string_dp(required_amounts.second, 2); + } + } + + if (!tooltip.is_empty()) { + static const StringName gathering_goods_localisation_key = "GOODS_PROJECT_LACK_GOODS"; + in_progress_unit_dict[military_info_unit_tooltip_key] = tr(gathering_goods_localisation_key) + tooltip; + } + + return in_progress_unit_dict; +} + +using leader_sort_func_t = bool (*)(LeaderBase const*, LeaderBase const*); + +static leader_sort_func_t _get_leader_sort_func(MenuSingleton::LeaderSortKey leader_sort_key) { + static const auto get_assignment = [](LeaderBase const* leader) -> std::string_view { + static const auto get_assignment_template = + [](LeaderBranched const* leader) -> std::string_view { + UnitInstanceGroup const* group = leader->get_unit_instance_group(); + return group != nullptr ? group->get_name() : std::string_view {}; + }; + + using enum UnitType::branch_t; + switch (leader->get_branch()) { + case LAND: + return get_assignment_template(static_cast(leader)); + case NAVAL: + return get_assignment_template(static_cast(leader)); + default: + return {}; + } + }; + + using enum MenuSingleton::LeaderSortKey; + + switch (leader_sort_key) { + case LEADER_SORT_PRESTIGE: + return [](LeaderBase const* a, LeaderBase const* b) -> bool { + return a->get_prestige() < b->get_prestige(); + }; + case LEADER_SORT_TYPE: + return [](LeaderBase const* a, LeaderBase const* b) -> bool { + return a->get_branch() < b->get_branch(); + }; + case LEADER_SORT_NAME: + return [](LeaderBase const* a, LeaderBase const* b) -> bool { + return a->get_name() < b->get_name(); + }; + case LEADER_SORT_ASSIGNMENT: + return [](LeaderBase const* a, LeaderBase const* b) -> bool { + return get_assignment(a) < get_assignment(b); + }; + default: + UtilityFunctions::push_error("Invalid miltiary menu leader sort key: ", leader_sort_key); + return [](LeaderBase const* a, LeaderBase const* b) -> bool { return false; }; + } +} + +template +using unit_group_sort_func_t = bool (*)(UnitInstanceGroup const*, UnitInstanceGroup const*); + +template +static unit_group_sort_func_t _get_unit_group_sort_func(MenuSingleton::UnitGroupSortKey unit_group_sort_key) { + using enum MenuSingleton::UnitGroupSortKey; + + switch (unit_group_sort_key) { + case UNIT_GROUP_SORT_NAME: + return [](UnitInstanceGroup const* a, UnitInstanceGroup const* b) -> bool { + return a->get_name() < b->get_name(); + }; + case UNIT_GROUP_SORT_STRENGTH: + return [](UnitInstanceGroup const* a, UnitInstanceGroup const* b) -> bool { + return a->get_unit_count() < b->get_unit_count(); + }; + default: + UtilityFunctions::push_error( + "Invalid miltiary menu ", Utilities::std_to_godot_string(UnitType::get_branched_unit_group_name(Branch)), + " sort key: ", unit_group_sort_key + ); + return [](UnitInstanceGroup const* a, UnitInstanceGroup const* b) -> bool { return false; }; + } +} + +Dictionary MenuSingleton::get_military_menu_info( + LeaderSortKey leader_sort_key, bool sort_leaders_descending, + UnitGroupSortKey army_sort_key, bool sort_armies_descending, + UnitGroupSortKey navy_sort_key, bool sort_navies_descending +) { + cached_leader_dicts.clear(); + + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, {}); + + DefinitionManager const& definition_manager = game_singleton->get_definition_manager(); + ModifierEffectCache const& modifier_effect_cache = definition_manager.get_modifier_manager().get_modifier_effect_cache(); + StaticModifierCache const& static_modifier_cache = definition_manager.get_modifier_manager().get_static_modifier_cache(); + IssueManager const& issue_manager = definition_manager.get_politics_manager().get_issue_manager(); + + CountryInstance const* country = game_singleton->get_viewed_country(); + if (country == nullptr) { + return {}; + } + + Dictionary ret; + + // Military stats + static const StringName military_info_war_exhaustion_key = "war_exhaustion"; + static const StringName military_info_war_exhaustion_tooltip_key = "war_exhaustion_tooltip"; + static const StringName military_info_supply_consumption_key = "supply_consumption"; + static const StringName military_info_supply_consumption_tooltip_key = "supply_consumption_tooltip"; + static const StringName military_info_organisation_regain_key = "organisation_regain"; + static const StringName military_info_organisation_regain_tooltip_key = "organisation_regain_tooltip"; + static const StringName military_info_land_organisation_key = "land_organisation"; + static const StringName military_info_land_organisation_tooltip_key = "land_organisation_tooltip"; + static const StringName military_info_naval_organisation_key = "naval_organisation"; + static const StringName military_info_naval_organisation_tooltip_key = "naval_organisation_tooltip"; + static const StringName military_info_land_unit_start_experience_key = "land_unit_start_experience"; + static const StringName military_info_naval_unit_start_experience_key = "naval_unit_start_experience"; + static const StringName military_info_unit_start_experience_tooltip_key = "unit_start_experience_tooltip"; + static const StringName military_info_recruit_time_key = "recruit_time"; + static const StringName military_info_recruit_time_tooltip_key = "recruit_time_tooltip"; + static const StringName military_info_combat_width_key = "combat_width"; + static const StringName military_info_combat_width_tooltip_key = "combat_width_tooltip"; + static const StringName military_info_digin_cap_key = "digin_cap"; + static const StringName military_info_military_tactics_key = "military_tactics"; + + static const String war_exhaustion_template_string = "%s/%s"; + const String war_exhaustion_string = Utilities::fixed_point_to_string_dp(country->get_war_exhaustion(), 2); + const String max_war_exhaustion_string = Utilities::fixed_point_to_string_dp(country->get_war_exhaustion_max(), 2); + ret[military_info_war_exhaustion_key] = vformat( + war_exhaustion_template_string, war_exhaustion_string, max_war_exhaustion_string + ); + + static const StringName war_exhaution_localisation_key = "MILITARY_WAR_EXHAUSTION_TOOLTIP"; + static const StringName max_war_exhaution_localisation_key = "MILITARY_MAX_WAR_EXHAUSTION_TOOLTIP"; + static const StringName value_replace_key = "$VALUE$"; + static const String current_effects_localisation_key = "WEX_EFFECTS"; + static const String war_exhaustion_tooltip_template_string = "%s%s\n\n%s%s" + get_tooltip_separator() + "%s%s"; + + ret[military_info_war_exhaustion_tooltip_key] = vformat( + war_exhaustion_tooltip_template_string, + tr(war_exhaution_localisation_key).replace(value_replace_key, war_exhaustion_string), + _make_modifier_effect_contributions_tooltip(*country, *modifier_effect_cache.get_war_exhaustion()), + tr(max_war_exhaution_localisation_key).replace(value_replace_key, max_war_exhaustion_string), + _make_modifier_effect_contributions_tooltip(*country, *modifier_effect_cache.get_max_war_exhaustion()), + tr(current_effects_localisation_key), + _make_modifier_effects_tooltip(static_modifier_cache.get_war_exhaustion() * country->get_war_exhaustion()) + ); + + static const StringName base_value_percent_localisation_key = "MILITARY_BASEVALUE_PERCENT"; + const String base_value_percent_tooltip = tr(base_value_percent_localisation_key); + + ret[military_info_supply_consumption_key] = country->get_supply_consumption().to_float(); + ret[military_info_supply_consumption_tooltip_key] = base_value_percent_tooltip + _make_modifier_effect_contributions_tooltip( + *country, *modifier_effect_cache.get_supply_consumption() + ); + + static const StringName from_tech_localisation_key = "FROM_TECHNOLOGY"; + const String from_technology_tooltip = "\n" + tr(from_tech_localisation_key) + ": "; + + ret[military_info_organisation_regain_key] = country->get_organisation_regain().to_float(); + { + String organisation_regain_tooltip = base_value_percent_tooltip; + const fixed_point_t morale = country->get_modifier_effect_value(*modifier_effect_cache.get_morale_global()); + if (morale != fixed_point_t::_0()) { + organisation_regain_tooltip += from_technology_tooltip + _make_modifier_effect_value_coloured( + *modifier_effect_cache.get_morale_global(), morale, true + ); + } + organisation_regain_tooltip += _make_modifier_effect_contributions_tooltip( + *country, *modifier_effect_cache.get_org_regain() + ); + ret[military_info_organisation_regain_tooltip_key] = std::move(organisation_regain_tooltip); + } + + ret[military_info_land_organisation_key] = country->get_land_organisation().to_float(); + ret[military_info_land_organisation_tooltip_key] = base_value_percent_tooltip + _make_modifier_effect_contributions_tooltip( + *country, *modifier_effect_cache.get_land_organisation() + ); + + ret[military_info_naval_organisation_key] = country->get_naval_organisation().to_float(); + ret[military_info_naval_organisation_tooltip_key] = base_value_percent_tooltip + _make_modifier_effect_contributions_tooltip( + *country, *modifier_effect_cache.get_naval_organisation() + ); + + ret[military_info_land_unit_start_experience_key] = country->get_land_unit_start_experience().to_float(); + ret[military_info_naval_unit_start_experience_key] = country->get_naval_unit_start_experience().to_float(); + { + static const StringName base_value_localisation_key = "MILITARY_BASEVALUE"; + String unit_start_experience_tooltip = tr(base_value_localisation_key); + const fixed_point_t regular_experience_level = country->get_modifier_effect_value( + *modifier_effect_cache.get_regular_experience_level() + ); + if (regular_experience_level != fixed_point_t::_0()) { + unit_start_experience_tooltip += from_technology_tooltip + _make_modifier_effect_value_coloured( + *modifier_effect_cache.get_regular_experience_level(), regular_experience_level, true + ); + } + const String land_unit_start_experience_tooltip = _make_modifier_effect_contributions_tooltip( + *country, *modifier_effect_cache.get_land_unit_start_experience() + ); + const String naval_unit_start_experience_tooltip = _make_modifier_effect_contributions_tooltip( + *country, *modifier_effect_cache.get_naval_unit_start_experience() + ); + unit_start_experience_tooltip += land_unit_start_experience_tooltip; + if (!land_unit_start_experience_tooltip.is_empty() && !naval_unit_start_experience_tooltip.is_empty()) { + unit_start_experience_tooltip += "\n"; + } + unit_start_experience_tooltip += naval_unit_start_experience_tooltip; + ret[military_info_unit_start_experience_tooltip_key] = std::move(unit_start_experience_tooltip); + } + + ret[military_info_recruit_time_key] = country->get_recruit_time().to_float(); + ret[military_info_recruit_time_tooltip_key] = base_value_percent_tooltip + _make_modifier_effect_contributions_tooltip( + *country, *modifier_effect_cache.get_unit_recruitment_time() + ); + + ret[military_info_combat_width_key] = country->get_combat_width(); + { + static const StringName base_value_combat_width_localisation_key = "COMWID_BASE"; + static const String val_replace_key = "$VAL$"; + String combat_width_tooltip = tr(base_value_combat_width_localisation_key).replace( + val_replace_key, String::num_uint64( + definition_manager.get_define_manager().get_military_defines().get_base_combat_width() + ) + ); + const fixed_point_t combat_width = country->get_modifier_effect_value( + *modifier_effect_cache.get_combat_width_additive() + ); + if (combat_width != fixed_point_t::_0()) { + combat_width_tooltip += from_technology_tooltip + GUILabel::get_colour_marker() + "G" + + String::num_int64(combat_width.to_int64_t()); + } + ret[military_info_combat_width_tooltip_key] = std::move(combat_width_tooltip); + } + + ret[military_info_digin_cap_key] = country->get_digin_cap(); + ret[military_info_military_tactics_key] = country->get_military_tactics().to_float(); + + // Mobilisation + static const StringName military_info_is_mobilised_key = "is_mobilised"; + static const StringName military_info_mobilisation_progress_key = "mobilisation_progress"; + static const StringName military_info_mobilisation_size_key = "mobilisation_size"; + static const StringName military_info_mobilisation_size_tooltip_key = "mobilisation_size_tooltip"; + static const StringName military_info_mobilisation_impact_tooltip_key = "mobilisation_impact_tooltip"; + static const StringName military_info_mobilisation_economy_impact_key = "mobilisation_economy_impact"; + static const StringName military_info_mobilisation_economy_impact_tooltip_key = "mobilisation_economy_impact_tooltip"; + + ret[military_info_is_mobilised_key] = country->is_mobilised(); + // TODO - get mobilisation progress from SIM + // ret[military_info_mobilisation_progress_key] = country->get_mobilisation_progress().to_float(); + ret[military_info_mobilisation_size_key] = static_cast(country->get_mobilisation_potential_regiment_count()); + + static const StringName mobilisation_size_tooltip_localisation_key = "MOB_SIZE_IRO"; + static const String mobilisation_size_tooltip_replace_value_key = "$VALUE$"; + + ret[military_info_mobilisation_size_tooltip_key] = tr(mobilisation_size_tooltip_localisation_key).replace( + mobilisation_size_tooltip_replace_value_key, Utilities::fixed_point_to_string_dp( + country->get_modifier_effect_value(*modifier_effect_cache.get_mobilisation_size()) * 100, 2 + ) + ) + _make_modifier_effect_contributions_tooltip(*country, *modifier_effect_cache.get_mobilisation_size()); + + if (!country->is_mobilised()) { + ret[military_info_mobilisation_impact_tooltip_key] = _make_mobilisation_impact_tooltip(); + } + + ret[military_info_mobilisation_economy_impact_key] = country->get_mobilisation_economy_impact().to_float(); + + { + fixed_point_t research_contribution; + + String mobilisation_economy_impact_tooltip = _make_modifier_effect_contributions_tooltip( + *country, *modifier_effect_cache.get_mobilisation_economy_impact(), &research_contribution + ); + + if (research_contribution != fixed_point_t::_0()) { + static const StringName research_contribution_negative_key = "MOB_ECO_IMPACT"; + static const StringName research_contribution_positive_key = "MOB_ECO_PENALTY"; + static const String replace_value_key = "$VALUE$"; + + mobilisation_economy_impact_tooltip = tr( + research_contribution < fixed_point_t::_0() + ? research_contribution_negative_key + : research_contribution_positive_key + ).replace( + replace_value_key, _make_modifier_effect_value( + *modifier_effect_cache.get_mobilisation_economy_impact(), research_contribution.abs(), false + ) + ) + mobilisation_economy_impact_tooltip; + } else if (!mobilisation_economy_impact_tooltip.is_empty()) { + // Remove leading newline + mobilisation_economy_impact_tooltip = mobilisation_economy_impact_tooltip.substr(1); + } + + ret[military_info_mobilisation_economy_impact_tooltip_key] = mobilisation_economy_impact_tooltip; + } + + // Leaders + static const StringName military_info_general_count_key = "general_count"; + static const StringName military_info_admiral_count_key = "admiral_count"; + static const StringName military_info_create_leader_count_key = "create_leader_count"; + static const StringName military_info_create_leader_cost_key = "create_leader_cost"; + static const StringName military_info_auto_create_leaders_key = "auto_create_leaders"; + static const StringName military_info_auto_assign_leaders_key = "auto_assign_leaders"; + static const StringName military_info_leaders_list_key = "leaders_list"; + + ret[military_info_general_count_key] = static_cast(country->get_general_count()); + ret[military_info_admiral_count_key] = static_cast(country->get_admiral_count()); + const uint64_t create_leader_count = country->get_create_leader_count(); + if (create_leader_count > 0) { + ret[military_info_create_leader_count_key] = static_cast(country->get_create_leader_count()); + } else { + ret[military_info_create_leader_cost_key] = + definition_manager.get_define_manager().get_military_defines().get_leader_recruit_cost().to_float(); + } + ret[military_info_auto_create_leaders_key] = country->get_auto_create_leaders(); + ret[military_info_auto_assign_leaders_key] = country->get_auto_assign_leaders(); + + if (country->has_leaders()) { + std::vector sorted_leaders; + sorted_leaders.reserve(country->get_leader_count()); + for (General const& general : country->get_generals()) { + sorted_leaders.push_back(&general); + } + for (Admiral const& admiral : country->get_admirals()) { + sorted_leaders.push_back(&admiral); + } + + if (leader_sort_key != LEADER_SORT_NONE) { + const leader_sort_func_t leader_sort_func = _get_leader_sort_func(leader_sort_key); + + if (sort_leaders_descending) { + std::sort( + sorted_leaders.begin(), sorted_leaders.end(), + [leader_sort_func](LeaderBase const* a, LeaderBase const* b) -> bool { + return leader_sort_func(b, a); + } + ); + } else { + std::sort(sorted_leaders.begin(), sorted_leaders.end(), leader_sort_func); + } + } + + TypedArray leaders; + if (leaders.resize(sorted_leaders.size()) == OK) { + + for (size_t index = 0; index < sorted_leaders.size(); ++index) { + leaders[index] = make_leader_dict(*sorted_leaders[index]); + } + + ret[military_info_leaders_list_key] = std::move(leaders); + } else { + UtilityFunctions::push_error( + "Failed to resize military menu leaders array to the correct size (", + static_cast(sorted_leaders.size()), ") for country \"", + Utilities::std_to_godot_string(country->get_identifier()), "\"" + ); + } + } + + // Armies and Navies + static const StringName military_info_is_disarmed_key = "is_disarmed"; + static const StringName military_info_armies_key = "armies"; + static const StringName military_info_in_progress_brigades_key = "in_progress_brigades"; + static const StringName military_info_navies_key = "navies"; + static const StringName military_info_in_progress_ships_key = "in_progress_ships"; + + ret[military_info_is_disarmed_key] = country->is_disarmed(); + + using enum UnitType::branch_t; + + if (country->has_armies()) { + std::vector sorted_armies; + sorted_armies.reserve(country->get_army_count()); + for (ArmyInstance const* army : country->get_armies()) { + sorted_armies.push_back(army); + } + + if (army_sort_key != UNIT_GROUP_SORT_NONE) { + const unit_group_sort_func_t army_sort_func = _get_unit_group_sort_func(army_sort_key); + + if (sort_armies_descending) { + std::sort( + sorted_armies.begin(), sorted_armies.end(), + [army_sort_func](UnitInstanceGroup const* a, UnitInstanceGroup const* b) -> bool { + return army_sort_func(b, a); + } + ); + } else { + std::sort(sorted_armies.begin(), sorted_armies.end(), army_sort_func); + } + } + + TypedArray armies; + if (armies.resize(sorted_armies.size()) == OK) { + + for (size_t index = 0; index < sorted_armies.size(); ++index) { + armies[index] = make_unit_group_dict(*sorted_armies[index]); + } + + ret[military_info_armies_key] = std::move(armies); + } else { + UtilityFunctions::push_error( + "Failed to resize military menu armies array to the correct size (", + static_cast(sorted_armies.size()), ") for country \"", + Utilities::std_to_godot_string(country->get_identifier()), "\"" + ); + } + } + + { + TypedArray in_progress_brigades; + + in_progress_brigades.push_back(make_in_progress_unit_dict()); + + ret[military_info_in_progress_brigades_key] = in_progress_brigades; + } + + if (country->has_navies()) { + std::vector sorted_navies; + sorted_navies.reserve(country->get_navy_count()); + for (NavyInstance const* navy : country->get_navies()) { + sorted_navies.push_back(navy); + } + + if (navy_sort_key != UNIT_GROUP_SORT_NONE) { + const unit_group_sort_func_t navy_sort_func = _get_unit_group_sort_func(navy_sort_key); + + if (sort_navies_descending) { + std::sort( + sorted_navies.begin(), sorted_navies.end(), + [navy_sort_func](UnitInstanceGroup const* a, UnitInstanceGroup const* b) -> bool { + return navy_sort_func(b, a); + } + ); + } else { + std::sort(sorted_navies.begin(), sorted_navies.end(), navy_sort_func); + } + } + + TypedArray navies; + if (navies.resize(sorted_navies.size()) == OK) { + + for (size_t index = 0; index < sorted_navies.size(); ++index) { + navies[index] = make_unit_group_dict(*sorted_navies[index]); + } + + ret[military_info_navies_key] = std::move(navies); + } else { + UtilityFunctions::push_error( + "Failed to resize military menu navies array to the correct size (", + static_cast(sorted_navies.size()), ") for country \"", + Utilities::std_to_godot_string(country->get_identifier()), "\"" + ); + } + } + + { + TypedArray in_progress_ships; + + in_progress_ships.push_back(make_in_progress_unit_dict()); + + ret[military_info_in_progress_ships_key] = in_progress_ships; + } + + return ret; +} + /* Find/Search Panel */ Error MenuSingleton::generate_search_cache() { @@ -901,7 +1830,7 @@ Error MenuSingleton::generate_search_cache() { for (StateSet const& state_set : state_sets) { for (State const& state : state_set.get_states()) { - String display_name = get_state_name(state); + String display_name = _get_state_name(state); String search_name = display_name.to_lower(); search_panel.entry_cache.push_back({ @@ -914,7 +1843,7 @@ Error MenuSingleton::generate_search_cache() { for (CountryInstance const& country : countries) { // TODO - replace with a proper "exists" check if (country.get_capital() != nullptr) { - String display_name = get_country_name(country); + String display_name = _get_country_name(country); String search_name = display_name.to_lower(); search_panel.entry_cache.push_back({ diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.hpp b/extension/src/openvic-extension/singletons/MenuSingleton.hpp index 786a277a..b315c629 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -22,7 +23,9 @@ namespace OpenVic { struct CountryParty; struct RebelType; struct ModifierValue; + struct ModifierSum; struct RuleSet; + struct LeaderBase; class MenuSingleton : public godot::Object { GDCLASS(MenuSingleton, godot::Object) @@ -93,6 +96,15 @@ namespace OpenVic { std::vector pops, filtered_pops; }; + enum LeaderSortKey { + LEADER_SORT_NONE, LEADER_SORT_PRESTIGE, LEADER_SORT_TYPE, LEADER_SORT_NAME, LEADER_SORT_ASSIGNMENT, + MAX_LEADER_SORT_KEY + }; + ordered_map cached_leader_dicts; + enum UnitGroupSortKey { + UNIT_GROUP_SORT_NONE, UNIT_GROUP_SORT_NAME, UNIT_GROUP_SORT_STRENGTH, MAX_UNIT_GROUP_SORT_KEY + }; + struct search_panel_t { struct entry_t { std::variant target; @@ -120,14 +132,22 @@ namespace OpenVic { * the given position. */ static godot::StringName const& _signal_update_tooltip(); - godot::String get_state_name(State const& state) const; - godot::String get_country_name(CountryInstance const& country) const; - godot::String get_country_adjective(CountryInstance const& country) const; + godot::String _get_state_name(State const& state) const; + godot::String _get_country_name(CountryInstance const& country) const; + godot::String _get_country_adjective(CountryInstance const& country) const; + + godot::String _make_modifier_effects_tooltip(ModifierValue const& modifier) const; - // Modifier effect and rule tooltips begin with a newline character (unless they're empty), as they're always - // added after a starting/title section. - godot::String make_modifier_effects_tooltip(ModifierValue const& modifier) const; - godot::String make_rules_tooltip(RuleSet const& rules) const; + template + requires std::same_as || std::same_as + godot::String _make_modifier_effect_contributions_tooltip( + T const& modifier_sum, ModifierEffect const& effect, fixed_point_t* tech_contributions = nullptr, + fixed_point_t* other_contributions = nullptr, godot::String const& prefix = "\n" + ) const; + + godot::String _make_rules_tooltip(RuleSet const& rules) const; + + godot::String _make_mobilisation_impact_tooltip() const; protected: static void _bind_methods(); @@ -205,6 +225,17 @@ namespace OpenVic { /* Array of GFXPieChartTexture::godot_pie_chart_data_t. */ godot::TypedArray get_population_menu_distribution_info() const; + /* MILITARY MENU */ + godot::Dictionary make_leader_dict(LeaderBase const& leader); + template + godot::Dictionary make_unit_group_dict(UnitInstanceGroup const& unit_group); + godot::Dictionary make_in_progress_unit_dict() const; + godot::Dictionary get_military_menu_info( + LeaderSortKey leader_sort_key, bool sort_leaders_descending, + UnitGroupSortKey army_sort_key, bool sort_armies_descending, + UnitGroupSortKey navy_sort_key, bool sort_navies_descending + ); + /* Find/Search Panel */ // TODO - update on country government type change and state creation/destruction // (which automatically includes country creation/destruction) @@ -218,3 +249,5 @@ namespace OpenVic { VARIANT_ENUM_CAST(OpenVic::MenuSingleton::ProvinceListEntry); VARIANT_ENUM_CAST(OpenVic::MenuSingleton::PopSortKey); +VARIANT_ENUM_CAST(OpenVic::MenuSingleton::LeaderSortKey); +VARIANT_ENUM_CAST(OpenVic::MenuSingleton::UnitGroupSortKey); diff --git a/extension/src/openvic-extension/singletons/PopulationMenu.cpp b/extension/src/openvic-extension/singletons/PopulationMenu.cpp index 1e9e7dc6..7f7fa8db 100644 --- a/extension/src/openvic-extension/singletons/PopulationMenu.cpp +++ b/extension/src/openvic-extension/singletons/PopulationMenu.cpp @@ -109,7 +109,7 @@ TypedArray MenuSingleton::get_population_menu_province_list_rows(int country_dict[type_key] = LIST_ENTRY_COUNTRY; country_dict[index_key] = index; - country_dict[name_key] = menu_singleton.get_country_name(country_entry.country); + country_dict[name_key] = menu_singleton._get_country_name(country_entry.country); country_dict[size_key] = country_entry.country.get_total_population(); country_dict[change_key] = 0; country_dict[selected_key] = country_entry.selected; @@ -130,7 +130,7 @@ TypedArray MenuSingleton::get_population_menu_province_list_rows(int state_dict[type_key] = LIST_ENTRY_STATE; state_dict[index_key] = index; - state_dict[name_key] = menu_singleton.get_state_name(state_entry.state); + state_dict[name_key] = menu_singleton._get_state_name(state_entry.state); state_dict[size_key] = state_entry.state.get_total_population(); state_dict[change_key] = 0; state_dict[selected_key] = state_entry.selected; diff --git a/extension/src/openvic-extension/utility/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp index 70d2aa34..89011927 100644 --- a/extension/src/openvic-extension/utility/Utilities.cpp +++ b/extension/src/openvic-extension/utility/Utilities.cpp @@ -36,6 +36,33 @@ String Utilities::int_to_string_suffixed(int64_t val) { return (negative ? "-" : "") + String::num_int64(val); } +String Utilities::int_to_string_commas(int64_t val) { + const bool negative = val < 0; + if (negative) { + val = -val; + } + + const String string_val = String::num_int64(val); + + String result; + int64_t length_remaining = string_val.length(); + + static constexpr int64_t digits_per_comma = 3; + static const String comma = ","; + + while (length_remaining > digits_per_comma) { + result = comma + string_val.substr(length_remaining -= digits_per_comma, digits_per_comma) + result; + } + + result = string_val.substr(0, length_remaining) + result; + + if (negative) { + result = "-" + result; + } + + return result; +} + String Utilities::float_to_string_suffixed(float val) { const float abs_val = std::abs(val); @@ -60,7 +87,18 @@ String Utilities::float_to_string_suffixed(float val) { /* Float to string formatted with the specified number of decimal places. */ String Utilities::float_to_string_dp(float val, int32_t decimal_places) { - return String::num(val, decimal_places).pad_decimals(decimal_places); + String result = String::num(val, decimal_places); + if (decimal_places >= 0) { + return result.pad_decimals(decimal_places); + } else { + return result; + } +} + +String Utilities::fixed_point_to_string_dp(fixed_point_t val, int32_t decimal_places) { + // We could use fixed point's own to_string method, but that allocates an intermediate string so better to go via float + // return Utilities::std_to_godot_string(val.to_string(decimal_places)); + return Utilities::float_to_string_dp(val.to_float(), decimal_places); } String Utilities::float_to_string_dp_dynamic(float val) { @@ -68,6 +106,13 @@ String Utilities::float_to_string_dp_dynamic(float val) { return float_to_string_dp(val, abs_val < 2.0f ? 3 : abs_val < 10.0f ? 2 : 1); } +String Utilities::date_to_string(Date date) { + static const String date_template_string = String { "%d" } + Date::SEPARATOR_CHARACTER + "%d" + + Date::SEPARATOR_CHARACTER + "%d"; + + return vformat(date_template_string, date.get_year(), date.get_month(), date.get_day()); +} + /* Date formatted like one of these, with the month localised if possible: * - "1 January, 1836" (if month_first is false) * - "January 1, 1836" (if month_first is true) */ diff --git a/extension/src/openvic-extension/utility/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp index 98eabac8..39e92426 100644 --- a/extension/src/openvic-extension/utility/Utilities.hpp +++ b/extension/src/openvic-extension/utility/Utilities.hpp @@ -28,10 +28,14 @@ namespace OpenVic::Utilities { godot::String int_to_string_suffixed(int64_t val); + godot::String int_to_string_commas(int64_t val); + godot::String float_to_string_suffixed(float val); godot::String float_to_string_dp(float val, int32_t decimal_places); + godot::String fixed_point_to_string_dp(fixed_point_t val, int32_t decimal_places); + // 3dp if abs(val) < 2 else 2dp if abs(val) < 10 else 1dp godot::String float_to_string_dp_dynamic(float val); @@ -39,6 +43,8 @@ namespace OpenVic::Utilities { return static_cast(val); } + godot::String date_to_string(Date date); + godot::String date_to_formatted_string(Date date, bool month_first); _FORCE_INLINE_ godot::Color to_godot_color(IsColour auto colour) { diff --git a/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd b/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd index 11d40d15..fb440594 100644 --- a/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd @@ -4,19 +4,241 @@ var _active : bool = false const _screen : NationManagement.Screen = NationManagement.Screen.MILITARY +const _gui_file : String = "country_military" + +# Military stats +var _war_exhaustion_label : GUILabel +var _supply_consumption_label : GUILabel +var _organisation_regain_label : GUILabel +var _land_organisation_label : GUILabel +var _naval_organisation_label : GUILabel +var _unit_start_experience_label : GUILabel +var _recruit_time_label : GUILabel +var _combat_width_label : GUILabel +var _digin_cap_label : GUILabel +var _military_tactics_label : GUILabel + +# Mobilisation +var _mobilise_button : GUIIconButton +var _demobilise_button : GUIIconButton +var _mobilisation_progress_bar : GUIProgressBar +var _mobilisation_progress_label : GUILabel +var _mobilisation_size_label : GUILabel +var _mobilisation_economy_impact_label : GUILabel + +# Leaders +var _general_count_label : GUILabel +var _admiral_count_label : GUILabel +var _create_general_button : GUIIconButton +var _create_admiral_button : GUIIconButton +var _auto_create_leader_button : GUIIconButton +var _auto_assign_leader_button : GUIIconButton +var _leader_listbox : GUIListBox +var _leader_sort_key : MenuSingleton.LeaderSortKey = MenuSingleton.LeaderSortKey.LEADER_SORT_NONE +var _leader_sort_descending : bool = true + +# Armies and Navies +var _army_count_label : GUILabel +var _in_progress_brigade_count_label : GUILabel +var _disarmed_army_icon : GUIIcon +var _build_army_button : GUIIconButton +var _army_listbox : GUIListBox +var _army_sort_key : MenuSingleton.UnitGroupSortKey = MenuSingleton.UnitGroupSortKey.UNIT_GROUP_SORT_NONE +var _army_sort_descending : bool = true + +var _navy_count_label : GUILabel +var _in_progress_ship_count_label : GUILabel +var _disarmed_navy_icon : GUIIcon +var _build_navy_button : GUIIconButton +var _navy_listbox : GUIListBox +var _navy_sort_key : MenuSingleton.UnitGroupSortKey = MenuSingleton.UnitGroupSortKey.UNIT_GROUP_SORT_NONE +var _navy_sort_descending : bool = true + 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_military", "country_military") + add_gui_element(_gui_file, "country_military") + + var military_menu : Panel = get_panel_from_nodepath(^"./country_military") + if not military_menu: + return set_click_mask_from_nodepaths([^"./country_military/main_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_military/close_button") + var close_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(military_menu.get_node(^"./close_button")) if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) + # Military stats + var stats_panel : Panel = GUINode.get_panel_from_node(military_menu.get_node(^"./stats")) + if stats_panel: + _war_exhaustion_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./war_exhaustion")) + _supply_consumption_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./supply_consumption")) + _organisation_regain_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./org_regain")) + _land_organisation_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./army_org")) + _naval_organisation_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./navy_org")) + _unit_start_experience_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./unit_experience")) + _recruit_time_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./recruit_time")) + _combat_width_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./combat_width")) + _digin_cap_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./digin_cap")) + _military_tactics_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./tactics_level")) + + # Mobilisation + _mobilise_button = GUINode.get_gui_icon_button_from_node(military_menu.get_node(^"./mobilize")) + if _mobilise_button: + _mobilise_button.pressed.connect(func() -> void: print("MOBILISE PRESSED")) + _demobilise_button = GUINode.get_gui_icon_button_from_node(military_menu.get_node(^"./demobilize")) + if _demobilise_button: + _demobilise_button.pressed.connect(func() -> void: print("DEMOBILISE PRESSED")) + _demobilise_button.set_tooltip_string("$MILITARY_DEMOBILIZE$" + MenuSingleton.get_tooltip_separator() + "$MILITARY_DEMOBILIZE_DESC$") + _mobilisation_progress_bar = GUINode.get_gui_progress_bar_from_node(military_menu.get_node(^"./mobilize_progress")) + _mobilisation_progress_label = GUINode.get_gui_label_from_node(military_menu.get_node(^"./mobilize_progress_text")) + _mobilisation_size_label = GUINode.get_gui_label_from_node(military_menu.get_node(^"./mob_size")) + _mobilisation_economy_impact_label = GUINode.get_gui_label_from_node(military_menu.get_node(^"./mob_impact")) + + # Leaders + var leaders_panel : Panel = GUINode.get_panel_from_node(military_menu.get_node(^"./leaders")) + if leaders_panel: + _general_count_label = GUINode.get_gui_label_from_node(leaders_panel.get_node(^"./generals")) + _admiral_count_label = GUINode.get_gui_label_from_node(leaders_panel.get_node(^"./admirals")) + var sort_leader_prestige_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_prestige")) + if sort_leader_prestige_button: + sort_leader_prestige_button.pressed.connect( + func() -> void: + _leader_sort_key = MenuSingleton.LeaderSortKey.LEADER_SORT_PRESTIGE + _leader_sort_descending = not _leader_sort_descending + print("SORT LEADERS BY PRESTIGE ", "DESCENDING" if _leader_sort_descending else "ASCENDING") + _update_info() + ) + sort_leader_prestige_button.set_tooltip_string("SORT_BY_PRESTIGE") + var sort_leader_type_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_type")) + if sort_leader_type_button: + sort_leader_type_button.pressed.connect( + func() -> void: + _leader_sort_key = MenuSingleton.LeaderSortKey.LEADER_SORT_TYPE + _leader_sort_descending = not _leader_sort_descending + print("SORT LEADERS BY TYPE ", "DESCENDING" if _leader_sort_descending else "ASCENDING") + _update_info() + ) + sort_leader_type_button.set_tooltip_string("MILITARY_SORT_BY_TYPE_TOOLTIP") + var sort_leader_name_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_name")) + if sort_leader_name_button: + sort_leader_name_button.pressed.connect( + func() -> void: + _leader_sort_key = MenuSingleton.LeaderSortKey.LEADER_SORT_NAME + _leader_sort_descending = not _leader_sort_descending + print("SORT LEADERS BY NAME ", "DESCENDING" if _leader_sort_descending else "ASCENDING") + _update_info() + ) + sort_leader_name_button.set_tooltip_string("MILITARY_SORT_BY_NAME_TOOLTIP") + var sort_leader_army_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_army")) + if sort_leader_army_button: + sort_leader_army_button.set_text("MILITARY_SORT_ARMY") + sort_leader_army_button.pressed.connect( + func() -> void: + _leader_sort_key = MenuSingleton.LeaderSortKey.LEADER_SORT_ASSIGNMENT + _leader_sort_descending = not _leader_sort_descending + print("SORT LEADERS BY ASSIGNMENT ", "DESCENDING" if _leader_sort_descending else "ASCENDING") + _update_info() + ) + sort_leader_army_button.set_tooltip_string("MILITARY_SORT_BY_ASSIGNMENT_TOOLTIP") + _create_general_button = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./new_general")) + if _create_general_button: + _create_general_button.pressed.connect(func() -> void: print("CREATE GENERAL")) + _create_admiral_button = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./new_admiral")) + if _create_admiral_button: + _create_admiral_button.pressed.connect(func() -> void: print("CREATE ADMIRAL")) + _auto_create_leader_button = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./auto_create")) + if _auto_create_leader_button: + _auto_create_leader_button.toggled.connect(func(state : bool) -> void: print("AUTO CREATE LEADERS = ", state)) + _auto_create_leader_button.set_tooltip_string("MILITARY_AUTOCREATE_TOOLTIP") + _auto_assign_leader_button = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./auto_assign")) + if _auto_assign_leader_button: + _auto_assign_leader_button.toggled.connect(func(state : bool) -> void: print("AUTO ASSIGN LEADERS = ", state)) + _auto_assign_leader_button.set_tooltip_string("MILITARY_AUTOASSIGN_TOOLTIP") + _leader_listbox = GUINode.get_gui_listbox_from_node(military_menu.get_node(^"./leaders/leader_listbox")) + + # Armies and Navies + var army_pos : Vector2 = GUINode.get_gui_position(_gui_file, "army_pos") + var army_window : Panel = GUINode.generate_gui_element(_gui_file, "unit_window") + if army_window: + army_window.set_position(army_pos) + military_menu.add_child(army_window) + + _army_count_label = GUINode.get_gui_label_from_node(army_window.get_node(^"./current_count")) + _in_progress_brigade_count_label = GUINode.get_gui_label_from_node(army_window.get_node(^"./under_construction")) + _disarmed_army_icon = GUINode.get_gui_icon_from_node(army_window.get_node(^"./cut_down_to_size")) + + var sort_armies_name_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./sort_name")) + if sort_armies_name_button: + sort_armies_name_button.pressed.connect( + func() -> void: + _army_sort_key = MenuSingleton.UnitGroupSortKey.UNIT_GROUP_SORT_NAME + _army_sort_descending = not _army_sort_descending + print("SORT ARMIES BY NAME ", "DESCENDING" if _army_sort_descending else "ASCENDING") + _update_info() + ) + sort_armies_name_button.set_tooltip_string("MILITARY_SORT_BY_NAME_TOOLTIP") + var sort_armies_strength_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./sort_strength")) + if sort_armies_strength_button: + sort_armies_strength_button.pressed.connect( + func() -> void: + _army_sort_key = MenuSingleton.UnitGroupSortKey.UNIT_GROUP_SORT_STRENGTH + _army_sort_descending = not _army_sort_descending + print("SORT ARMIES BY STRENGTH ", "DESCENDING" if _army_sort_descending else "ASCENDING") + _update_info() + ) + sort_armies_strength_button.set_tooltip_string("MILITARY_SORT_BY_STRENGTH_TOOLTIP") + + _build_army_button = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./build_new")) + if _build_army_button: + _build_army_button.pressed.connect(func() -> void: "BUILD ARMY") + _build_army_button.set_text("MILITARY_BUILD_ARMY") + _build_army_button.set_tooltip_string("MILITARY_BUILD_ARMY_TOOLTIP") + + _army_listbox = GUINode.get_gui_listbox_from_node(army_window.get_node(^"./unit_listbox")) + + var navy_pos : Vector2 = GUINode.get_gui_position(_gui_file, "navy_pos") + var navy_window : Panel = GUINode.generate_gui_element(_gui_file, "unit_window") + if navy_window: + navy_window.set_position(navy_pos) + military_menu.add_child(navy_window) + + _navy_count_label = GUINode.get_gui_label_from_node(navy_window.get_node(^"./current_count")) + _in_progress_ship_count_label = GUINode.get_gui_label_from_node(navy_window.get_node(^"./under_construction")) + _disarmed_navy_icon = GUINode.get_gui_icon_from_node(navy_window.get_node(^"./cut_down_to_size")) + + var sort_navies_name_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./sort_name")) + if sort_navies_name_button: + sort_navies_name_button.pressed.connect( + func() -> void: + _navy_sort_key = MenuSingleton.UnitGroupSortKey.UNIT_GROUP_SORT_NAME + _navy_sort_descending = not _navy_sort_descending + print("SORT NAVIES BY NAME ", "DESCENDING" if _navy_sort_descending else "ASCENDING") + _update_info() + ) + sort_navies_name_button.set_tooltip_string("MILITARY_SORT_BY_NAME_TOOLTIP") + var sort_navies_strength_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./sort_strength")) + if sort_navies_strength_button: + sort_navies_strength_button.pressed.connect( + func() -> void: + _navy_sort_key = MenuSingleton.UnitGroupSortKey.UNIT_GROUP_SORT_STRENGTH + _navy_sort_descending = not _navy_sort_descending + print("SORT NAVIES BY STRENGTH ", "DESCENDING" if _navy_sort_descending else "ASCENDING") + _update_info() + ) + sort_navies_strength_button.set_tooltip_string("MILITARY_SORT_BY_STRENGTH_TOOLTIP") + + _build_navy_button = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./build_new")) + if _build_navy_button: + _build_navy_button.pressed.connect(func() -> void: "BUILD NAVY") + _build_navy_button.set_text("MILITARY_BUILD_NAVY") + _build_navy_button.set_tooltip_string("MILITARY_BUILD_NAVY_TOOLTIP") + + _navy_listbox = GUINode.get_gui_listbox_from_node(navy_window.get_node(^"./unit_listbox")) + _update_info() func _notification(what : int) -> void: @@ -28,9 +250,453 @@ func _on_update_active_nation_management_screen(active_screen : NationManagement _active = active_screen == _screen _update_info() +func _update_unit_group_list(listbox : GUIListBox, unit_groups : Array[Dictionary], in_progress_units : Array[Dictionary], is_army : bool) -> void: + var total_entry_count : int = unit_groups.size() + in_progress_units.size() + listbox.clear_children(total_entry_count) + while listbox.get_child_count() < total_entry_count: + var entry : Panel = GUINode.generate_gui_element(_gui_file, "unit_entry") + if not entry: + break + if not is_army: + var men_label : GUILabel = GUINode.get_gui_label_from_node(entry.get_node(^"./men")) + if men_label: + men_label.hide() + var digin_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry.get_node(^"./digin")) + if digin_icon: + digin_icon.hide() + listbox.add_child(entry) + + const military_info_unit_group_leader_picture_key : StringName = &"unit_group_leader_picture" + const military_info_unit_group_leader_tooltip_key : StringName = &"unit_group_leader_tooltip" + const military_info_unit_group_name_key : StringName = &"unit_group_name" + const military_info_unit_group_location_key : StringName = &"unit_group_location" + const military_info_unit_group_unit_count_key : StringName = &"unit_group_unit_count" + const military_info_unit_group_men_count_key : StringName = &"unit_group_men_count" # armies only + const military_info_unit_group_max_men_count_key : StringName = &"unit_group_max_men_count" # armies only + const military_info_unit_group_morale_key : StringName = &"unit_group_morale" + const military_info_unit_group_strength_key : StringName = &"unit_group_strength" + const military_info_unit_group_moving_tooltip_key : StringName = &"unit_group_moving_tooltip" + const military_info_unit_group_digin_tooltip_key : StringName = &"unit_group_digin_tooltip" # armies only + const military_info_unit_group_combat_key : StringName = &"unit_group_combat" + + for index : int in mini(unit_groups.size(), listbox.get_child_count()): + var entry_menu : Panel = GUINode.get_panel_from_node(listbox.get_child(index)) + var unit_group_dict : Dictionary = unit_groups[index] + + var entry_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_unit_entry_bg")) + if entry_button: + # TODO - sort out repeat connections!!! + entry_button.pressed.connect(func() -> void: print("OPENING UNIT GROUP")) + var unit_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./unit_progress")) + if unit_progress_bar: + unit_progress_bar.hide() + var leader_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) + if leader_icon: + var leader_texture : Texture2D = unit_group_dict.get(military_info_unit_group_leader_picture_key, null) + if leader_texture: + leader_icon.show() + leader_icon.set_texture(leader_texture) + leader_icon.set_tooltip_string(unit_group_dict.get(military_info_unit_group_leader_tooltip_key, "")) + else: + leader_icon.hide() + var unit_strip_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./unit_strip")) + if unit_strip_icon: + unit_strip_icon.hide() + var name_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) + if name_label: + name_label.set_text(unit_group_dict.get(military_info_unit_group_name_key, "")) + var location_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) + if location_label: + location_label.set_text(GUINode.format_province_name(unit_group_dict.get(military_info_unit_group_location_key, ""))) + var unit_eta_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./unit_eta")) + if unit_eta_label: + unit_eta_label.hide() + var unit_count_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) + if unit_count_label: + unit_count_label.show() + var unit_count_string : String = str(unit_group_dict.get(military_info_unit_group_unit_count_key, 0)) + unit_count_label.set_text(unit_count_string) + unit_count_label.set_tooltip_string(tr(&"MILITARY_REGIMENTS_TOOLTIP" if is_army else &"MILITARY_SHIPS_TOOLTIP").replace("$VALUE$", unit_count_string)) + var strength : float = unit_group_dict.get(military_info_unit_group_strength_key, 0) + var strength_tooltip : String = tr(&"MILITARY_STRENGTH_TOOLTIP2" if is_army else &"MILITARY_SHIPSTRENGTH_TOOLTIP2").replace("$PERCENT$", str(int(strength * 100))) + if is_army: + var men_count : int = unit_group_dict.get(military_info_unit_group_men_count_key, 0) if is_army else 0 + var max_men_count : int = unit_group_dict.get(military_info_unit_group_max_men_count_key, 0) if is_army else 0 + var men_count_string : String = GUINode.int_to_string_commas(men_count) if is_army else "" + strength_tooltip = strength_tooltip.replace("$VALUE$", men_count_string).replace("$MAX$", GUINode.int_to_string_commas(max_men_count)) + var men_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./men")) + if men_label: + men_label.show() + men_label.set_text(men_count_string) + men_label.set_tooltip_string(strength_tooltip) + var military_cancel_unit_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_cancel_unit")) + if military_cancel_unit_button: + military_cancel_unit_button.hide() + var morale_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./morale_progress")) + if morale_progress_bar: + morale_progress_bar.show() + var morale : float = unit_group_dict.get(military_info_unit_group_morale_key, 0.0) + morale_progress_bar.set_value_no_signal(morale) + morale_progress_bar.set_tooltip_string(tr(&"MILITARY_MORALE_TOOLTIP").replace("$VALUE$", str(int(morale * 100)))) + var strength_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./strength_progress")) + if strength_progress_bar: + strength_progress_bar.show() + strength_progress_bar.set_value_no_signal(strength) + strength_progress_bar.set_tooltip_string(strength_tooltip) + var moving_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./moving")) + if moving_icon: + var moving_tooltip : String = unit_group_dict.get(military_info_unit_group_moving_tooltip_key, "") + moving_icon.set_visible(not moving_tooltip.is_empty()) + moving_icon.set_tooltip_string(moving_tooltip) + var digin_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./digin")) + if is_army: + if digin_icon: + var digin_tooltip : String = unit_group_dict.get(military_info_unit_group_digin_tooltip_key, "") + digin_icon.set_visible(not digin_tooltip.is_empty()) + digin_icon.set_tooltip_string(digin_tooltip) + var combat_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./combat")) + if combat_icon: + combat_icon.set_visible(unit_group_dict.get(military_info_unit_group_combat_key, false)) + + const military_info_unit_progress_key : StringName = &"unit_progress" + const military_info_unit_icon_key : StringName = &"unit_icon" + const military_info_unit_name_key : StringName = &"unit_name" + const military_info_unit_location_key : StringName = &"unit_location" + const military_info_unit_eta_key : StringName = &"unit_eta" + const military_info_unit_tooltip_key : StringName = &"unit_tooltip" + + for index : int in clampi(listbox.get_child_count() - unit_groups.size(), 0, in_progress_units.size()): + var entry_menu : Panel = GUINode.get_panel_from_node(listbox.get_child(index + unit_groups.size())) + var unit_dict : Dictionary = in_progress_units[index] + var unit_tooltip : String = unit_dict.get(military_info_unit_tooltip_key, "") + + var unit_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./unit_progress")) + if unit_progress_bar: + unit_progress_bar.show() + unit_progress_bar.set_value_no_signal(unit_dict.get(military_info_unit_progress_key, 0)) + # This is enough to show the tooltip everywhere we need, the only place + # in the base game that obviously also has it is the ETA date label + unit_progress_bar.set_tooltip_string(unit_tooltip) + var leader_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) + if leader_icon: + leader_icon.hide() + var unit_strip_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./unit_strip")) + if unit_strip_icon: + unit_strip_icon.show() + unit_strip_icon.set_icon_index(unit_dict.get(military_info_unit_icon_key, 0)) + var name_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) + if name_label: + name_label.set_text(unit_dict.get(military_info_unit_name_key, "")) + var location_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) + if location_label: + location_label.set_text(GUINode.format_province_name(unit_dict.get(military_info_unit_location_key, ""))) + var unit_eta_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./unit_eta")) + if unit_eta_label: + unit_eta_label.show() + unit_eta_label.set_text(unit_dict.get(military_info_unit_eta_key, "")) + var unit_count_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) + if unit_count_label: + unit_count_label.hide() + if is_army: + var men_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./men")) + if men_label: + men_label.hide() + var military_cancel_unit_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_cancel_unit")) + if military_cancel_unit_button: + military_cancel_unit_button.show() + # TODO - sort out repeat connections!!! + military_cancel_unit_button.pressed.connect(func() -> void: print("CANCELLED UNIT CONSTRUCTION")) + var morale_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./morale_progress")) + if morale_progress_bar: + morale_progress_bar.hide() + var strength_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./strength_progress")) + if strength_progress_bar: + strength_progress_bar.hide() + var moving_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./moving")) + if moving_icon: + moving_icon.hide() + if is_army: + var digin_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./digin")) + if digin_icon: + digin_icon.hide() + var combat_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./combat")) + if combat_icon: + combat_icon.hide() + func _update_info() -> void: if _active: - # TODO - update UI state + # Military stats + const military_info_war_exhaustion_key : StringName = &"war_exhaustion" + const military_info_war_exhaustion_tooltip_key : StringName = &"war_exhaustion_tooltip" + const military_info_supply_consumption_key : StringName = &"supply_consumption" + const military_info_supply_consumption_tooltip_key : StringName = &"supply_consumption_tooltip" + const military_info_organisation_regain_key : StringName = &"organisation_regain" + const military_info_organisation_regain_tooltip_key : StringName = &"organisation_regain_tooltip" + const military_info_land_organisation_key : StringName = &"land_organisation" + const military_info_land_organisation_tooltip_key : StringName = &"land_organisation_tooltip" + const military_info_naval_organisation_key : StringName = &"naval_organisation" + const military_info_naval_organisation_tooltip_key : StringName = &"naval_organisation_tooltip" + const military_info_land_unit_start_experience_key : StringName = &"land_unit_start_experience" + const military_info_naval_unit_start_experience_key : StringName = &"naval_unit_start_experience" + const military_info_unit_start_experience_tooltip_key : StringName = &"unit_start_experience_tooltip" + const military_info_recruit_time_key : StringName = &"recruit_time" + const military_info_recruit_time_tooltip_key : StringName = &"recruit_time_tooltip" + const military_info_combat_width_key : StringName = &"combat_width" + const military_info_combat_width_tooltip_key : StringName = &"combat_width_tooltip" + const military_info_digin_cap_key : StringName = &"digin_cap" + const military_info_military_tactics_key : StringName = &"military_tactics" + + # Mobilisation + const military_info_is_mobilised_key : StringName = &"is_mobilised" + const military_info_mobilisation_progress_key : StringName = &"mobilisation_progress" + const military_info_mobilisation_size_key : StringName = &"mobilisation_size" + const military_info_mobilisation_size_tooltip_key : StringName = &"mobilisation_size_tooltip" + const military_info_mobilisation_impact_tooltip_key : StringName = &"mobilisation_impact_tooltip" + const military_info_mobilisation_economy_impact_key : StringName = &"mobilisation_economy_impact" + const military_info_mobilisation_economy_impact_tooltip_key : StringName = &"mobilisation_economy_impact_tooltip" + + # Leaders + const military_info_general_count_key : StringName = &"general_count" + const military_info_admiral_count_key : StringName = &"admiral_count" + const military_info_create_leader_count_key : StringName = &"create_leader_count" + const military_info_create_leader_cost_key : StringName = &"create_leader_cost" + const military_info_auto_create_leaders_key : StringName = &"auto_create_leaders" + const military_info_auto_assign_leaders_key : StringName = &"auto_assign_leaders" + const military_info_leaders_list_key : StringName = &"leaders_list" + + # Armies and Navies + const military_info_is_disarmed_key : StringName = &"is_disarmed" + const military_info_armies_key : StringName = &"armies" + const military_info_in_progress_brigades_key : StringName = &"in_progress_brigades" + const military_info_navies_key : StringName = &"navies" + const military_info_in_progress_ships_key : StringName = &"in_progress_ships" + + var military_info : Dictionary = MenuSingleton.get_military_menu_info( + _leader_sort_key, _leader_sort_descending, + _army_sort_key, _army_sort_descending, + _navy_sort_key, _navy_sort_descending + ) + + # Military stats + if _war_exhaustion_label: + _war_exhaustion_label.set_text(military_info.get(military_info_war_exhaustion_key, "0.00/0.00")) + _war_exhaustion_label.set_tooltip_string(military_info.get(military_info_war_exhaustion_tooltip_key, "")) + if _supply_consumption_label: + _supply_consumption_label.set_text("%d%%" % int(100 * military_info.get(military_info_supply_consumption_key, 0))) + _supply_consumption_label.set_tooltip_string(military_info.get(military_info_supply_consumption_tooltip_key, "")) + if _organisation_regain_label: + _organisation_regain_label.set_text("%d%%" % int(100 * military_info.get(military_info_organisation_regain_key, 0))) + _organisation_regain_label.set_tooltip_string(military_info.get(military_info_organisation_regain_tooltip_key, "")) + if _land_organisation_label: + _land_organisation_label.set_text("%d%%" % int(100 * military_info.get(military_info_land_organisation_key, 0))) + _land_organisation_label.set_tooltip_string(military_info.get(military_info_land_organisation_tooltip_key, "")) + if _naval_organisation_label: + _naval_organisation_label.set_text("%d%%" % int(100 * military_info.get(military_info_naval_organisation_key, 0))) + _naval_organisation_label.set_tooltip_string(military_info.get(military_info_naval_organisation_tooltip_key, "")) + if _unit_start_experience_label: + _unit_start_experience_label.set_text( + "%s/%s" % [ + GUINode.float_to_string_dp(military_info.get(military_info_land_unit_start_experience_key, 0), 2), + GUINode.float_to_string_dp(military_info.get(military_info_naval_unit_start_experience_key, 0), 2) + ] + ) + _unit_start_experience_label.set_tooltip_string(military_info.get(military_info_unit_start_experience_tooltip_key, "")) + if _recruit_time_label: + _recruit_time_label.set_text("%d%%" % int(100 * military_info.get(military_info_recruit_time_key, 0))) + _recruit_time_label.set_tooltip_string(military_info.get(military_info_recruit_time_tooltip_key, "")) + if _combat_width_label: + _combat_width_label.set_text(str(military_info.get(military_info_combat_width_key, 0))) + _combat_width_label.set_tooltip_string(military_info.get(military_info_combat_width_tooltip_key, "")) + if _digin_cap_label: + _digin_cap_label.set_text(str(military_info.get(military_info_digin_cap_key, 0))) + if _military_tactics_label: + _military_tactics_label.set_text("%s%%" % GUINode.float_to_string_dp(100 * military_info.get(military_info_military_tactics_key, 0), 2)) + + # Mobilisation + var is_mobilised : bool = military_info.get(military_info_is_mobilised_key, false) + var mobilisation_size : int = military_info.get(military_info_mobilisation_size_key, 0) + var can_mobilise : bool = mobilisation_size > 0 + var mobilisation_size_tooltip : String = military_info.get(military_info_mobilisation_size_tooltip_key, "") + var mobilisation_impact_tooltip : String = military_info.get(military_info_mobilisation_impact_tooltip_key, "") + if _mobilise_button: + _mobilise_button.set_visible(not is_mobilised) + _mobilise_button.set_disabled(not can_mobilise) + _mobilise_button.set_tooltip_string( + tr(&"MILITARY_MOBILIZE") + "\n" + mobilisation_size_tooltip + + ("\n\n" + tr(&"NOT_ENOUGH_FOR_BRIGADE") + "\n\n" if not can_mobilise else "\n\n\n") + + mobilisation_impact_tooltip + MenuSingleton.get_tooltip_separator() + tr(&"MILITARY_MOBILIZE_DESC") + ) + if _demobilise_button: + _demobilise_button.set_visible(is_mobilised) + var mobilisation_progress : float = military_info.get(military_info_mobilisation_progress_key, 0) + var mobilisation_progress_string : String = GUINode.float_to_string_dp(100 * mobilisation_progress, 2) + if _mobilisation_progress_bar: + _mobilisation_progress_bar.set_value_no_signal(mobilisation_progress) + _mobilisation_progress_bar.set_tooltip_string( + tr(&"MOBILIZATION_PROGRESS_PENDING").replace("$PROG$", mobilisation_progress_string) if is_mobilised else "MOBILIZATION_PROGRESS_NOT_MOBILIZED" + ) + if _mobilisation_progress_label: + _mobilisation_progress_label.set_text("%s%%" % mobilisation_progress_string) + if _mobilisation_size_label: + _mobilisation_size_label.set_text(str(mobilisation_size)) + _mobilisation_size_label.set_tooltip_string( + mobilisation_size_tooltip + ("" if mobilisation_impact_tooltip.is_empty() else "\n\n\n" + mobilisation_impact_tooltip) + ) + if _mobilisation_economy_impact_label: + _mobilisation_economy_impact_label.set_text("%s%%" % GUINode.float_to_string_dp(100 * military_info.get(military_info_mobilisation_economy_impact_key, 0), 2)) + _mobilisation_economy_impact_label.set_tooltip_string(military_info.get(military_info_mobilisation_economy_impact_tooltip_key, "")) + + # Leaders + if _general_count_label: + _general_count_label.set_text(str(military_info.get(military_info_general_count_key, 0))) + if _admiral_count_label: + _admiral_count_label.set_text(str(military_info.get(military_info_admiral_count_key, 0))) + var create_leader_count : int = military_info.get(military_info_create_leader_count_key, 0) + var can_create_leaders : bool = create_leader_count > 0 + var create_leader_count_string : String = str(create_leader_count) + var create_leader_cost_tooltip : String = tr(&"MILITARY_LACK_OF_LEADER").replace("$VALUE$", str(military_info.get(military_info_create_leader_cost_key, 0))) + # TODO - leave the main text set and only update $VALUE$ using substitution dict functionality once buttons get proper text support + if _create_general_button: + _create_general_button.set_text(tr(&"MILITARY_CREATE_GENERAL").replace("$VALUE$", create_leader_count_string)) + _create_general_button.set_disabled(not can_create_leaders) + _create_general_button.set_tooltip_string( + tr(&"MILITARY_NEW_GENERAL_TOOLTIP").replace("$VALUE$", create_leader_count_string) if can_create_leaders else create_leader_cost_tooltip + ) + if _create_admiral_button: + _create_admiral_button.set_text(tr(&"MILITARY_CREATE_ADMIRAL").replace("$VALUE$", create_leader_count_string)) + _create_admiral_button.set_disabled(not can_create_leaders) + _create_admiral_button.set_tooltip_string( + tr(&"MILITARY_NEW_ADMIRAL_TOOLTIP").replace("$VALUE$", create_leader_count_string) if can_create_leaders else create_leader_cost_tooltip + ) + ## TODO - look into why set_pressed_no_signal didn't work + if _auto_create_leader_button: + _auto_create_leader_button.set_pressed(military_info.get(military_info_auto_create_leaders_key, false)) + if _auto_assign_leader_button: + _auto_assign_leader_button.set_pressed(military_info.get(military_info_auto_assign_leaders_key, false)) + if _leader_listbox: + var leader_entries : Array[Dictionary] = military_info.get(military_info_leaders_list_key, [] as Array[Dictionary]) + _leader_listbox.clear_children(leader_entries.size()) + while _leader_listbox.get_child_count() < leader_entries.size(): + var unit_entry : Panel = GUINode.generate_gui_element(_gui_file, "milview_leader_entry") + if not unit_entry: + break + _leader_listbox.add_child(unit_entry) + + const military_info_leader_name_key : StringName = &"leader_name" + const military_info_leader_picture_key : StringName = &"leader_picture" + const military_info_leader_prestige_key : StringName = &"leader_prestige" + const military_info_leader_prestige_tooltip_key : StringName = &"leader_prestige_tooltip" + const military_info_leader_background_key : StringName = &"leader_background" + const military_info_leader_personality_key : StringName = &"leader_personality" + const military_info_leader_can_be_used_key : StringName = &"leader_can_be_used" + const military_info_leader_assignment_key : StringName = &"leader_assignment" + const military_info_leader_location_key : StringName = &"leader_location" + const military_info_leader_tooltip_key : StringName = &"leader_tooltip" + + for index : int in mini(leader_entries.size(), _leader_listbox.get_child_count()): + var entry_menu : Panel = GUINode.get_panel_from_node(_leader_listbox.get_child(index)) + var leader_dict : Dictionary = leader_entries[index] + + var prestige_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./leader_prestige_bar")) + if prestige_progress_bar: + prestige_progress_bar.set_value_no_signal(leader_dict.get(military_info_leader_prestige_key, 0)) + prestige_progress_bar.set_tooltip_string(leader_dict.get(military_info_leader_prestige_tooltip_key, "")) + + var leader_name : String = leader_dict.get(military_info_leader_name_key, "") + var leader_tooltip : String = leader_dict.get(military_info_leader_tooltip_key, "") + + var entry_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./military_leader_entry_bg")) + if entry_icon: + entry_icon.set_tooltip_string(leader_tooltip) + var leader_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) + if leader_icon: + var leader_texture : Texture2D = leader_dict.get(military_info_leader_picture_key, null) + if leader_texture: + leader_icon.set_texture(leader_texture) + leader_icon.show() + else: + leader_icon.hide() + leader_icon.set_tooltip_string(leader_tooltip) + var name_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) + if name_label: + name_label.set_text(leader_name) + name_label.set_tooltip_string(leader_tooltip) + var background_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./background")) + if background_label: + background_label.set_text(leader_dict.get(military_info_leader_background_key, "")) + background_label.set_tooltip_string(leader_tooltip) + var personality_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./personality")) + if personality_label: + personality_label.set_text(leader_dict.get(military_info_leader_personality_key, "")) + personality_label.set_tooltip_string(leader_tooltip) + var use_leader_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./use_leader")) + if use_leader_button: + # TODO - investigate why "set_pressed_no_signal" wasn't enough + use_leader_button.set_pressed(leader_dict.get(military_info_leader_can_be_used_key, false)) + # TODO - ensure only one connection? + use_leader_button.toggled.connect(func(state : bool) -> void: print("Toggled use_leader to ", state)) + use_leader_button.set_tooltip_string("USE_LEADER" if use_leader_button.is_pressed() else "") + + var army_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./army")) + var location_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) + + var leader_assignment_untype : Variant = leader_dict.get(military_info_leader_assignment_key, null) + if leader_assignment_untype != null: + var leader_assignment : String = leader_assignment_untype as String + var leader_location : String = GUINode.format_province_name(leader_dict.get(military_info_leader_location_key, ""), true) + var assignment_tooltip : String = tr(&"MILITARY_LEADER_NAME_TOOLTIP").replace("$NAME$", leader_name).replace("$ARMY$", leader_assignment).replace("$LOCATION$", leader_location) + + if army_label: + army_label.set_text(leader_assignment) + army_label.set_tooltip_string(assignment_tooltip) + if location_label: + location_label.set_text(leader_location) + location_label.set_tooltip_string(assignment_tooltip) + else: + if army_label: + army_label.set_text("MILITARY_UNASSIGNED") + army_label.clear_tooltip() + if location_label: + location_label.set_text("") + location_label.clear_tooltip() + + # Armies and Navies + var is_disarmed : bool = military_info.get(military_info_is_disarmed_key, false) + + var armies : Array[Dictionary] = military_info.get(military_info_armies_key, [] as Array[Dictionary]) + var in_progress_brigades : Array[Dictionary] = military_info.get(military_info_in_progress_brigades_key, [] as Array[Dictionary]) + if _army_count_label: + var army_size_string : String = str(armies.size()) + _army_count_label.set_text(army_size_string) + _army_count_label.set_tooltip_string(tr(&"MILITARY_ARMY_COUNT_TOOLTIP").replace("$VALUE$", army_size_string)) + if _in_progress_brigade_count_label: + var constructing_brigades_count_string : String = str(in_progress_brigades.size()) + _in_progress_brigade_count_label.set_text("(+%s)" % constructing_brigades_count_string) + _in_progress_brigade_count_label.set_tooltip_string(tr(&"MILITARY_ARMY_CONSTRUCTION_TOOLTIP").replace("$VALUE$", constructing_brigades_count_string)) + if _disarmed_army_icon: + _disarmed_army_icon.set_visible(is_disarmed) + if _build_army_button: + _build_army_button.set_disabled(is_disarmed) + if _army_listbox: + _update_unit_group_list(_army_listbox, armies, in_progress_brigades, true) + + var navies : Array[Dictionary] = military_info.get(military_info_navies_key, [] as Array[Dictionary]) + var in_progress_ships : Array[Dictionary] = military_info.get(military_info_in_progress_ships_key, [] as Array[Dictionary]) + if _navy_count_label: + var navy_size_string : String = str(navies.size()) + _navy_count_label.set_text(navy_size_string) + _navy_count_label.set_tooltip_string(tr(&"MILITARY_NAVY_COUNT_TOOLTIP").replace("$VALUE$", navy_size_string)) + if _in_progress_ship_count_label: + var constructing_ships_count_string : String = str(in_progress_ships.size()) + _in_progress_ship_count_label.set_text("(+%s)" % constructing_ships_count_string) + _in_progress_ship_count_label.set_tooltip_string(tr(&"MILITARY_NAVY_CONSTRUCTION_TOOLTIP").replace("$VALUE$", constructing_ships_count_string)) + if _disarmed_navy_icon: + _disarmed_navy_icon.set_visible(is_disarmed) + if _build_navy_button: + _build_navy_button.set_disabled(is_disarmed) + if _navy_listbox: + _update_unit_group_list(_navy_listbox, navies, in_progress_ships, false) + show() else: hide() diff --git a/game/src/Game/GameSession/Topbar.gd b/game/src/Game/GameSession/Topbar.gd index 4f917d29..1b0abeb5 100644 --- a/game/src/Game/GameSession/Topbar.gd +++ b/game/src/Game/GameSession/Topbar.gd @@ -520,27 +520,19 @@ func _update_info() -> void: _military_navy_size_label.set_text("§Y%d/%d" % [0, 0]) # TODO - navy size tooltip - const mobilised_key : StringName = &"mobilised" + const is_mobilised_key : StringName = &"is_mobilised" const mobilisation_regiments_key : StringName = &"mobilisation_regiments" - const mobilisation_impact_key : StringName = &"mobilisation_impact" - const war_policy_key : StringName = &"war_policy" - const mobilisation_max_regiments_key : StringName = &"mobilisation_max_regiments" + const mobilisation_impact_tooltip_key : StringName = &"mobilisation_impact_tooltip" if _military_mobilisation_size_label: - if topbar_info.get(mobilised_key, false): + if topbar_info.get(is_mobilised_key, false): _military_mobilisation_size_label.set_text("§R-") _military_mobilisation_size_label.set_tooltip_string("TOPBAR_MOBILIZED") else: var mobilisation_regiments : String = str(topbar_info.get(mobilisation_regiments_key, 0)) - var mobilisation_impact : String = GUINode.float_to_string_dp(topbar_info.get(mobilisation_impact_key, 0), 1) + "%" - - _military_mobilisation_size_label.set_text("§Y" + mobilisation_regiments) - _military_mobilisation_size_label.set_tooltip_string_and_substitution_dict( - tr(&"TOPBAR_MOBILIZE_TOOLTIP") + "\n\n" + tr(&"MOBILIZATION_IMPACT_LIMIT_DESC") + "\n" + tr(&"MOBILIZATION_IMPACT_LIMIT_DESC2").replace("$CURR$", "$CURR2$"), - { - "CURR": mobilisation_regiments, "IMPACT": mobilisation_impact, "POLICY": topbar_info.get(war_policy_key, ""), - "UNITS": str(topbar_info.get(mobilisation_max_regiments_key, 0)), "CURR2": regiment_count - } + _military_mobilisation_size_label.set_text("§Y%s" % mobilisation_regiments) + _military_mobilisation_size_label.set_tooltip_string( + tr(&"TOPBAR_MOBILIZE_TOOLTIP").replace("$CURR$", mobilisation_regiments) + "\n\n" + topbar_info.get(mobilisation_impact_tooltip_key, "") ) if _military_leadership_points_label: