diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation index 384f3d99..519797b5 160000 --- a/extension/deps/openvic-simulation +++ b/extension/deps/openvic-simulation @@ -1 +1 @@ -Subproject commit 384f3d99391cb8aad3e684c66035b1913adef8a3 +Subproject commit 519797b5098c87866e4c3e2a92885794be63907d 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..8aa39735 100644 --- a/extension/doc_classes/MenuSingleton.xml +++ b/extension/doc_classes/MenuSingleton.xml @@ -55,6 +55,12 @@ + + + + + + @@ -326,5 +332,17 @@ + + + + + + + + + + + + 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/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 14e04e24..33f9d589 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 854b9bb6..6b7c8ab9 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,123 @@ 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 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 = "+"; } - String result; + static constexpr int32_t DECIMAL_PLACES = 2; - for (auto const& [effect, value] : modifier.get_values()) { - static const String post_name_text = ": " + GUILabel::get_colour_marker(); + using enum ModifierEffect::format_t; - result += "\n" + tr(Utilities::std_to_godot_string(effect->get_localisation_key())) + post_name_text; + switch (format_effect.get_format()) { + case PROPORTION_DECIMAL: + value *= 100; + [[fallthrough]]; + case PERCENTAGE_DECIMAL: + result += Utilities::std_to_godot_string(value.to_string(DECIMAL_PLACES) + "%"); + break; + case INT: - if (value == 0) { - result += "Y"; - } else if (effect->is_positive_good() == (value > 0)) { - result += "G"; - } else { - result += "R"; - } + // TODO - remove test code?!?! - if (value >= 0) { - result += "+"; + if (!value.is_integer() || value - fixed_point_t::parse(value.to_int64_t()) != fixed_point_t::_0()) { + Logger::error( + "Error formatting value for ModifierEffect \"", format_effect.get_identifier(), "\": ", value, + " is not an integer! Replacing with ", value.to_int64_t() + ); } - static constexpr int32_t DECIMAL_PLACES = 2; + result += String::num_int64(value.to_int64_t()); + break; + case RAW_DECIMAL: [[fallthrough]]; + default: // Use raw decimal as fallback format + result += Utilities::std_to_godot_string(value.to_string(DECIMAL_PLACES)); + break; + } - using enum ModifierEffect::format_t; + return result; +} - 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; - } +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; - static const String end_text = GUILabel::get_colour_marker() + String { "!" }; - result += end_text; + return result; +} + +String MenuSingleton::_make_modifier_line( + std::string_view identifier, ModifierEffect const& format_effect, fixed_point_t value, bool plus_for_non_negative +) const { + return tr(Utilities::std_to_godot_string(identifier)) + ": " + + _make_modifier_effect_value_coloured(format_effect, value, plus_for_non_negative); +} + +String MenuSingleton::_make_modifier_effects_tooltip(ModifierValue const& modifier) const { + String result; + + for (auto const& [effect, value] : modifier.get_values()) { + result += "\n" + _make_modifier_line(effect->get_identifier(), *effect, value, true); } return result; } -String MenuSingleton::make_rules_tooltip(RuleSet const& rules) const { +template +String MenuSingleton::_make_modifier_effect_contributions_tooltip( + ModifierSum const& sum, ModifierEffect const& effect, ENTRY_CHECK_CALLBACK check_entry_callback +) const { + String result; + + sum.for_each_contributing_modifier( + effect, + [this, &effect, &check_entry_callback, &result]( + ModifierSum::modifier_entry_t const& modifier_entry + ) -> void { + if (check_entry_callback(modifier_entry)) { + if (!result.is_empty()) { + result += "\n"; + } + + result += _make_modifier_line( + modifier_entry.modifier.get_identifier(), + effect, + modifier_entry.get_modifier_effect_value(effect), + true + ); + } + } + ); + + return result; +} + +template +String MenuSingleton::_make_modifier_effect_contributions_tooltip_nullcheck( + ModifierSum const& sum, ModifierEffect const* effect, ENTRY_CHECK_CALLBACK check_entry_callback +) const { + return effect != nullptr ? _make_modifier_effect_contributions_tooltip(sum, *effect, check_entry_callback) : String {}; +} + +String MenuSingleton::_make_rules_tooltip(RuleSet const& rules) const { if (rules.empty()) { return {}; } @@ -187,6 +253,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::std_to_godot_string((country->get_mobilisation_impact() * 100).to_string(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 +395,16 @@ 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" }); + + 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); + /* Find/Search Panel */ OV_BIND_METHOD(MenuSingleton::generate_search_cache); OV_BIND_METHOD(MenuSingleton::update_search_results, { "text" }); @@ -326,7 +453,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 +472,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 */ @@ -450,7 +577,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(); @@ -495,7 +622,55 @@ Dictionary MenuSingleton::get_province_info_from_index(int32_t index) const { ProductionType const& production_type = *rgo.get_production_type_nullable(); String contributing_modifier_effects; - // TODO - generate list of contributing modifier effects (including combined "From Technology" effeects) + + { + ModifierEffectCache const& modifier_effect_cache = + game_singleton->get_definition_manager().get_modifier_manager().get_modifier_effect_cache(); + + ModifierEffect const* rgo_size_local; + ModifierEffect const* rgo_size_global; + + if (production_type.is_farm()) { + rgo_size_local = modifier_effect_cache.get_farm_rgo_size_local(); + rgo_size_global = modifier_effect_cache.get_farm_rgo_size_global(); + } else { + rgo_size_local = modifier_effect_cache.get_mine_rgo_size_local(); + rgo_size_global = modifier_effect_cache.get_mine_rgo_size_global(); + } + + if (rgo_size_local != nullptr) { + if (province->get_terrain_type() != nullptr) { + const fixed_point_t from_terrain = province->get_terrain_type()->get_effect(*rgo_size_local); + + if (from_terrain != fixed_point_t::_0()) { + contributing_modifier_effects = tr( + Utilities::std_to_godot_string(province->get_terrain_type()->get_identifier()) + ) + ": " + _make_modifier_effect_value_coloured(*rgo_size_local, from_terrain, false) + "\n"; + } + } + + const fixed_point_t from_province = province->get_modifier_effect_value(*rgo_size_local); + + if (from_province != fixed_point_t::_0()) { + static const StringName rgo_size_localisation_key = "RGO_SIZE"; + + contributing_modifier_effects += tr(rgo_size_localisation_key) + ": " + + _make_modifier_effect_value_coloured(*rgo_size_local, from_province, false) + "\n"; + } + } + + const fixed_point_t from_technology = province->get_modifier_effect_value_nullcheck(rgo_size_global) + + province->get_modifier_effect_value_nullcheck( + modifier_effect_cache.get_good_effects()[production_type.get_output_good()].get_rgo_size() + ); + + if (from_technology != fixed_point_t::_0()) { + static const StringName from_technology_localisation_key = "employ_from_tech"; + + contributing_modifier_effects += tr(from_technology_localisation_key) + + _make_modifier_effect_value_coloured(*rgo_size_global, from_technology, false); + } + } static const StringName employment_localisation_key = "PROVINCEVIEW_EMPLOYMENT"; static const String value_replace_key = "$VALUE$"; @@ -676,7 +851,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 +869,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 +945,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 +1042,602 @@ 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(); + + UnitInstanceGroupBranched 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_nullcheck(modifier_effect_cache.get_morale_leader()); + organisation_bonus = modifier_value.get_effect_nullcheck(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; +} + +Dictionary MenuSingleton::make_army_dict(ArmyInstance const& army) { + static const StringName military_info_army_leader_picture_key = "army_leader_picture"; + static const StringName military_info_army_leader_tooltip_key = "army_leader_tooltip"; + static const StringName military_info_army_name_key = "army_name"; + static const StringName military_info_army_location_key = "army_location"; + static const StringName military_info_army_regiment_count_key = "army_regiment_count"; + static const StringName military_info_army_men_count_key = "army_men_count"; + static const StringName military_info_army_max_men_count_key = "army_max_men_count"; + static const StringName military_info_army_morale_key = "army_morale"; + static const StringName military_info_army_moving_tooltip_key = "army_moving_tooltip"; + static const StringName military_info_army_digin_tooltip_key = "army_digin_tooltip"; + static const StringName military_info_army_combat_key = "army_combat"; + + Dictionary army_dict; + + if (army.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(*army.get_leader()); + + army_dict[military_info_army_leader_picture_key] = leader_dict.get( + military_info_leader_picture_key, Ref {} + ); + army_dict[military_info_army_leader_tooltip_key] = leader_dict.get(military_info_leader_tooltip_key, String {}); + } + + army_dict[military_info_army_name_key] = Utilities::std_to_godot_string(army.get_name()); + if (army.get_position() != nullptr) { + army_dict[military_info_army_location_key] = Utilities::std_to_godot_string(army.get_position()->get_identifier()); + } + army_dict[military_info_army_regiment_count_key] = static_cast(army.get_unit_count()); + + // TODO - calculate (max) men and morale properly in ArmyInstance and RegimentInstances and set here + army_dict[military_info_army_men_count_key] = static_cast(army.get_unit_count() * 3000); + army_dict[military_info_army_max_men_count_key] = static_cast(army.get_unit_count() * 3000); + army_dict[military_info_army_morale_key] = 1.0f; + + if (true /* army.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 {}; + + army_dict[military_info_army_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::std_to_godot_string(arrival_date.to_string())); + } + + if (true /* army.is_digging_in() */) { + static const StringName digin_localisation_key = "MILITARY_DIGIN_TOOLTIP"; + static const String moving_days_replace_key = "$DAYS$"; + + // TODO - get days spent digging in + int64_t days_spent_digging_in = 4; + + army_dict[military_info_army_digin_tooltip_key] = tr(digin_localisation_key).replace( + moving_days_replace_key, String::num_int64(days_spent_digging_in) + ); + } + + if (true /* army.is_in_combat() */) { + army_dict[military_info_army_combat_key] = true; + } + + return army_dict; +} + +Dictionary MenuSingleton::make_navy_dict(NavyInstance const& navy) { + static const StringName military_info_navy_leader_picture_key = "navy_leader_picture"; + static const StringName military_info_navy_leader_tooltip_key = "navy_leader_tooltip"; + static const StringName military_info_navy_name_key = "navy_name"; + static const StringName military_info_navy_location_key = "navy_location"; + static const StringName military_info_navy_ship_count_key = "navy_ship_count"; + static const StringName military_info_navy_morale_key = "navy_morale"; + static const StringName military_info_navy_strength_key = "navy_strength"; + static const StringName military_info_navy_moving_tooltip_key = "navy_moving_tooltip"; + static const StringName military_info_navy_combat_key = "navy_combat"; + + Dictionary navy_dict; + + if (navy.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(*navy.get_leader()); + + navy_dict[military_info_navy_leader_picture_key] = leader_dict.get( + military_info_leader_picture_key, Ref {} + ); + navy_dict[military_info_navy_leader_tooltip_key] = leader_dict.get(military_info_leader_tooltip_key, String {}); + } + + navy_dict[military_info_navy_name_key] = Utilities::std_to_godot_string(navy.get_name()); + if (navy.get_position() != nullptr) { + navy_dict[military_info_navy_location_key] = Utilities::std_to_godot_string(navy.get_position()->get_identifier()); + } + navy_dict[military_info_navy_ship_count_key] = static_cast(navy.get_unit_count()); + + // TODO - calculate strength and morale properly in NavyInstance from ShipInstances and set here + navy_dict[military_info_navy_morale_key] = 1.0f; + navy_dict[military_info_navy_strength_key] = 1.0f; + + if (true /* navy.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 {}; + + navy_dict[military_info_navy_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::std_to_godot_string(arrival_date.to_string())); + } + + if (true /* navy.is_in_combat() */) { + navy_dict[military_info_navy_combat_key] = true; + } + + return navy_dict; +} + +Dictionary MenuSingleton::get_military_menu_info(LeaderSortKey leader_sort_key) { + 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(); + 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_max_key = "war_exhaustion_max"; + static const StringName military_info_supply_consumption_key = "supply_consumption"; + static const StringName military_info_organisation_regain_key = "organisation_regain"; + static const StringName military_info_land_organisation_key = "land_organisation"; + static const StringName military_info_naval_organisation_key = "naval_organisation"; + 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_recruit_time_key = "recruit_time"; + static const StringName military_info_combat_width_key = "combat_width"; + static const StringName military_info_digin_cap_key = "digin_cap"; + static const StringName military_info_military_tactics_key = "military_tactics"; + + ret[military_info_war_exhaustion_key] = country->get_war_exhaustion().to_float(); + ret[military_info_war_exhaustion_max_key] = country->get_war_exhaustion_max().to_float(); + ret[military_info_supply_consumption_key] = country->get_supply_consumption().to_float(); + ret[military_info_organisation_regain_key] = country->get_organisation_regain().to_float(); + ret[military_info_land_organisation_key] = country->get_land_organisation().to_float(); + ret[military_info_naval_organisation_key] = country->get_naval_organisation().to_float(); + 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(); + ret[military_info_recruit_time_key] = country->get_recruit_time().to_float(); + ret[military_info_combat_width_key] = country->get_combat_width(); + 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(); + // 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::std_to_godot_string( + (country->get_modifier_effect_value_nullcheck(modifier_effect_cache.get_mobilisation_size()) * 100).to_string(2) + ) + ) + "\n" + _make_modifier_effect_contributions_tooltip_nullcheck( + country->get_modifier_sum(), modifier_effect_cache.get_mobilisation_size() + ); + + 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(); + + { + ModifierEffect const* mobilisation_economy_impact = modifier_effect_cache.get_mobilisation_economy_impact(); + fixed_point_t research_contribution; + + String mobilisation_economy_impact_tooltip = _make_modifier_effect_contributions_tooltip_nullcheck( + country->get_modifier_sum(), + mobilisation_economy_impact, + [mobilisation_economy_impact, &research_contribution]( + ModifierSum::modifier_entry_t const& modifier_entry + ) -> bool { + using enum Modifier::modifier_type_t; + + switch (modifier_entry.modifier.get_type()) { + case TECHNOLOGY: + [[fallthrough]]; + case INVENTION: + research_contribution += modifier_entry.get_modifier_effect_value(*mobilisation_economy_impact); + return false; + + default: + return true; + } + } + ); + + if (research_contribution != fixed_point_t::_0()) { + if (!mobilisation_economy_impact_tooltip.is_empty()) { + mobilisation_economy_impact_tooltip = "\n" + mobilisation_economy_impact_tooltip; + } + + 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(*mobilisation_economy_impact, research_contribution.abs(), false) + ) + mobilisation_economy_impact_tooltip; + } + + 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_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()); + ret[military_info_create_leader_count_key] = static_cast(country->get_create_leader_count()); + 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); + } + + switch (leader_sort_key) { + case LEADER_SORT_PRESTIGE: + std::sort( + sorted_leaders.begin(), sorted_leaders.end(), + [](LeaderBase const* a, LeaderBase const* b) -> bool { + return a->get_prestige() > b->get_prestige(); + } + ); + break; + case LEADER_SORT_TYPE: + std::sort( + sorted_leaders.begin(), sorted_leaders.end(), + [](LeaderBase const* a, LeaderBase const* b) -> bool { + return a->get_branch() > b->get_branch(); + } + ); + break; + case LEADER_SORT_NAME: + std::sort( + sorted_leaders.begin(), sorted_leaders.end(), + [](LeaderBase const* a, LeaderBase const* b) -> bool { + return a->get_name() > b->get_name(); + } + ); + break; + case LEADER_SORT_ASSIGNMENT: + //std::sort( + // sorted_leaders.begin(), sorted_leaders.end(), + // [](LeaderBase const* a, LeaderBase const* b) -> bool { + // // Do we have to convert to provinces or armies? + // return a->get_prestige() > b->get_prestige(); + // } + //); + break; + default: break; + } + + 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(); + + 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); + } + + // TODO - sort armies... + + TypedArray armies; + if (armies.resize(sorted_armies.size()) == OK) { + + for (size_t index = 0; index < sorted_armies.size(); ++index) { + armies[index] = make_army_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()), "\"" + ); + } + } + + // ret[military_info_in_progress_brigades_key] = TypedArray {}; + + 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); + } + + // TODO - sort navies... + + TypedArray navies; + if (navies.resize(sorted_navies.size()) == OK) { + + for (size_t index = 0; index < sorted_navies.size(); ++index) { + navies[index] = make_navy_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()), "\"" + ); + } + } + + // ret[military_info_in_progress_ships_key] = TypedArray {}; + + /* + Each army/navy needs: leader icon, name, location, regiments, men (only for land), morale, strength, moving, digin, combat + Each regiment/ship needs: build progress, unit_eta, name & icon of unit type, location + */ + + return ret; +} + /* Find/Search Panel */ Error MenuSingleton::generate_search_cache() { @@ -901,7 +1667,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 +1680,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..e2474d42 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include #include @@ -22,7 +24,17 @@ namespace OpenVic { struct CountryParty; struct RebelType; struct ModifierValue; + struct ModifierSum; struct RuleSet; + struct LeaderBase; + + template + concept ModifierEntryCallback = NodeTools::Callback; + + // ModifierEntryCallbacks are used to filter out entries that should not be included in tooltips, and maybe also collect + // them together to be displayed in a different way. If the callback returns true for an entry then that entry will be + // included in the tooltip normally, otherwise it will be ignored and left to the caller who provided the callback. + static constexpr bool _allow_all_modifier_entries(ModifierSum::modifier_entry_t const&) { return true; } class MenuSingleton : public godot::Object { GDCLASS(MenuSingleton, godot::Object) @@ -93,6 +105,12 @@ 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; + struct search_panel_t { struct entry_t { std::variant target; @@ -120,14 +138,33 @@ 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; // 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; + godot::String _make_modifier_line( + std::string_view identifier, ModifierEffect const& format_effect, fixed_point_t value, bool plus_for_non_negative + ) const; + + godot::String _make_modifier_effects_tooltip(ModifierValue const& modifier) const; + + template + godot::String _make_modifier_effect_contributions_tooltip( + ModifierSum const& sum, ModifierEffect const& effect, + ENTRY_CHECK_CALLBACK check_entry_callback = _allow_all_modifier_entries + ) const; + + template + godot::String _make_modifier_effect_contributions_tooltip_nullcheck( + ModifierSum const& sum, ModifierEffect const* effect, + ENTRY_CHECK_CALLBACK check_entry_callback = _allow_all_modifier_entries + ) const; + + godot::String _make_rules_tooltip(RuleSet const& rules) const; + + godot::String _make_mobilisation_impact_tooltip() const; protected: static void _bind_methods(); @@ -205,6 +242,12 @@ 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); + godot::Dictionary make_army_dict(ArmyInstance const& army); + godot::Dictionary make_navy_dict(NavyInstance const& navy); + godot::Dictionary get_military_menu_info(LeaderSortKey leader_sort_key); + /* Find/Search Panel */ // TODO - update on country government type change and state creation/destruction // (which automatically includes country creation/destruction) @@ -218,3 +261,4 @@ namespace OpenVic { VARIANT_ENUM_CAST(OpenVic::MenuSingleton::ProvinceListEntry); VARIANT_ENUM_CAST(OpenVic::MenuSingleton::PopSortKey); +VARIANT_ENUM_CAST(OpenVic::MenuSingleton::LeaderSortKey); diff --git a/extension/src/openvic-extension/singletons/PopulationMenu.cpp b/extension/src/openvic-extension/singletons/PopulationMenu.cpp index e2db7c7b..c944e93b 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..2c64456c 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); diff --git a/extension/src/openvic-extension/utility/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp index 98eabac8..d5051d99 100644 --- a/extension/src/openvic-extension/utility/Utilities.hpp +++ b/extension/src/openvic-extension/utility/Utilities.hpp @@ -28,6 +28,8 @@ 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); diff --git a/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd b/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd index 11d40d15..8e98be36 100644 --- a/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd @@ -4,19 +4,243 @@ 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 + +# 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 _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 + 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 + _update_info() + print("SORT LEADERS BY PRESTIGE") + ) + 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 + _update_info() + print("SORT LEADERS BY TYPE") + ) + 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 + _update_info() + print("SORT LEADERS BY NAME") + ) + 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 + _update_info() + print("SORT LEADERS BY ARMY") + ) + 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_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)) + _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: print("SORT ARMIES BY NAME")) + 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: print("SORT ARMIES BY STRENGTH")) + 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: print("SORT NAVIES BY NAME")) + 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: print("SORT NAVIES BY STRENGTH")) + 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")) + + # mobilize and demobilize GUIIconButtons (are both actually used?) + # mobilize_progress GUIProgressBar, mobilize_progress_text GUILabel + # mob_size and mob_impact GUILabels + + # leader Panel + # - generals and admirals GUILabels + # - sort_leader_prestige, sort_leader_type, sort_leader_name, sort_leader_army GUIIconButtons (sort_prestige_icon GUIIcon too) + # - new_general and new_admiral GUIIconButtons + # - auto_create and auto_assign GUIIconButton + # - leader_listbox GUIListBox + + # milview_leader_entry Panel + # - leader_prestige_bar GUIProgressBar + # - leader GUIIcon + # - name, background and personality GUILabels + # - use_leader GUIIconButton + # - army, location GUILabels + + # stats Panel + # - war_exhaustion, supply_consumption, org_regain, army_org, navy_org, unit_experience, recruit_time, combat_width, digin_cap, tactics_level GUILabels + + # army & navy unit_window Panels + # - current_count GUILabel + # - under_construction GUILabel + # - cut_down_to_size GUIIcon + # - sort_name and sort_strength GUIIconButtons + # - build_new GUIButton + # - unit_listbox GUIListBox + + # unit_entry Panel + # - unit_progress GUIProgressBar (used when building units) + # - leader and unit_strip GUIIcons (unit_strip used when building units) + # - name, location, unit_eta, regiments, men GUILabels + # - military_cancel_unit GUIIconButton + # - morale_progress, strength_progress GUIProgressBars + # - moving, digin, combat GUIIcons + _update_info() func _notification(what : int) -> void: @@ -30,7 +254,638 @@ func _on_update_active_nation_management_screen(active_screen : NationManagement 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_max_key : StringName = &"war_exhaustion_max" + const military_info_supply_consumption_key : StringName = &"supply_consumption" + const military_info_organisation_regain_key : StringName = &"organisation_regain" + const military_info_land_organisation_key : StringName = &"land_organisation" + const military_info_naval_organisation_key : StringName = &"naval_organisation" + 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_recruit_time_key : StringName = &"recruit_time" + const military_info_combat_width_key : StringName = &"combat_width" + 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_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) + + # Military stats + if _war_exhaustion_label: + _war_exhaustion_label.set_text( + "%s/%s" % [ + GUINode.float_to_string_dp(military_info.get(military_info_war_exhaustion_key, 0), 2), + GUINode.float_to_string_dp(military_info.get(military_info_war_exhaustion_max_key, 0), 2) + ] + ) + if _supply_consumption_label: + _supply_consumption_label.set_text("%d%%" % int(100 * military_info.get(military_info_supply_consumption_key, 0))) + if _organisation_regain_label: + _organisation_regain_label.set_text("%d%%" % int(100 * military_info.get(military_info_organisation_regain_key, 0))) + if _land_organisation_label: + _land_organisation_label.set_text("%d%%" % int(100 * military_info.get(military_info_land_organisation_key, 0))) + if _naval_organisation_label: + _naval_organisation_label.set_text("%d%%" % int(100 * military_info.get(military_info_naval_organisation_key, 0))) + 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) + ] + ) + if _recruit_time_label: + _recruit_time_label.set_text("%d%%" % int(100 * military_info.get(military_info_recruit_time_key, 0))) + if _combat_width_label: + _combat_width_label.set_text(str(military_info.get(military_info_combat_width_key, 0))) + 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 + # TODO - mobilisation button could be disabled? + var is_mobilised : bool = military_info.get(military_info_is_mobilised_key, false) + 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_tooltip_string( + + + # TODO - make sure elements only appear when they're meant to!!! + # in particular check out the case where you can't mobilise due to insufficient brigades + + + tr(&"MILITARY_MOBILIZE") + "\n" + mobilisation_size_tooltip + "\n\n" + (tr(&"NOT_ENOUGH_FOR_BRIGADE") if true else "") + + "\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(military_info.get(military_info_mobilisation_size_key, 0))) + _mobilisation_size_label.set_tooltip_string( + mobilisation_size_tooltip + ("" if mobilisation_size_tooltip.is_empty() or 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) + # 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$", str(create_leader_count))) + _create_general_button.set_disabled(create_leader_count < 1) + if _create_admiral_button: + _create_admiral_button.set_text(tr(&"MILITARY_CREATE_ADMIRAL").replace("$VALUE$", str(create_leader_count))) + _create_admiral_button.set_disabled(create_leader_count < 1) + ## 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")) + + if military_info_leader_assignment_key in leader_dict: + var leader_assignment : String = leader_dict.get(military_info_leader_assignment_key, "") + var leader_location : String = GUINode.format_province_name(leader_dict.get(military_info_leader_location_key, ""), true) + + # TODO - probably simpler to use directly translated and replaced string rather than set_tooltip_string_and_substitution_dict + const assignment_tooltip_string : String = "MILITARY_LEADER_NAME_TOOLTIP" + var assignment_tooltip_dict : Dictionary = { + "NAME": leader_name, + "ARMY": leader_assignment, + "LOCATION": leader_location + } + + if army_label: + army_label.set_text(leader_assignment) + army_label.set_tooltip_string_and_substitution_dict(assignment_tooltip_string, assignment_tooltip_dict) + if location_label: + location_label.set_text(leader_location) + location_label.set_tooltip_string_and_substitution_dict(assignment_tooltip_string, assignment_tooltip_dict) + 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) + + # unit_entry Panel + # - unit_progress GUIProgressBar (used when building units) + # - leader and unit_strip GUIIcons (unit_strip used when building units) + # - name, location, unit_eta, regiments, men GUILabels + # - military_cancel_unit GUIIconButton + # - morale_progress, strength_progress GUIProgressBars + # - moving, digin, combat GUIIcons + + 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]) + in_progress_brigades.push_back( + { + &"brigade_progress": 0.25, + &"brigade_icon": 1, + &"brigade_name": "Test Brigade", + &"brigade_location": "300", + &"brigade_eta": "2025.1.20", + &"brigade_tooltip": tr(&"GOODS_PROJECT_LACK_GOODS") + } + ) + 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: + var total_entry_count : int = armies.size() + in_progress_brigades.size() + _army_listbox.clear_children(total_entry_count) + while _army_listbox.get_child_count() < total_entry_count: + var army_entry : Panel = GUINode.generate_gui_element(_gui_file, "unit_entry") + if not army_entry: + break + _army_listbox.add_child(army_entry) + + const military_info_army_leader_picture_key : StringName = &"army_leader_picture" + const military_info_army_leader_tooltip_key : StringName = &"army_leader_tooltip" + const military_info_army_name_key : StringName = &"army_name" + const military_info_army_location_key : StringName = &"army_location" + const military_info_army_regiment_count_key : StringName = &"army_regiment_count" + const military_info_army_men_count_key : StringName = &"army_men_count" + const military_info_army_max_men_count_key : StringName = &"army_max_men_count" + const military_info_army_morale_key : StringName = &"army_morale" + const military_info_army_moving_tooltip_key : StringName = &"army_moving_tooltip" + const military_info_army_digin_tooltip_key : StringName = &"army_digin_tooltip" + const military_info_army_combat_key : StringName = &"army_combat" + + for index : int in mini(armies.size(), _army_listbox.get_child_count()): + var entry_menu : Panel = GUINode.get_panel_from_node(_army_listbox.get_child(index)) + var army_dict : Dictionary = armies[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 ARMY")) + 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 = army_dict.get(military_info_army_leader_picture_key, null) + if leader_texture: + leader_icon.show() + leader_icon.set_texture(leader_texture) + leader_icon.set_tooltip_string(army_dict.get(military_info_army_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(army_dict.get(military_info_army_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(army_dict.get(military_info_army_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 regiments_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) + if regiments_label: + regiments_label.show() + var regiment_count_string : String = str(army_dict.get(military_info_army_regiment_count_key, 0)) + regiments_label.set_text(regiment_count_string) + regiments_label.set_tooltip_string(tr(&"MILITARY_REGIMENTS_TOOLTIP").replace("$VALUE$", regiment_count_string)) + var men_count : int = army_dict.get(military_info_army_men_count_key, 0) + var max_men_count : int = army_dict.get(military_info_army_max_men_count_key, 0) + var strength : float = men_count / maxi(max_men_count, 1) + var men_count_string : String = GUINode.int_to_string_commas(men_count) + var strength_tooltip : String = tr(&"MILITARY_STRENGTH_TOOLTIP2").replace("$PERCENT$", str(int(strength * 100))).replace("$VALUE$", men_count_string).replace("$MAX$", GUINode.int_to_string_commas(max_men_count)) + # MILITARY_STRENGTH_TOOLTIP2;Unit strength is at �Y$PERCENT$%�W.\nStrength: $VALUE$/�Y$MAX$�W + # MILITARY_SHIPSTRENGTH_TOOLTIP2;Unit strength is at �Y$PERCENT$%�W. + 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 = army_dict.get(military_info_army_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 = army_dict.get(military_info_army_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 digin_icon: + var digin_tooltip : String = army_dict.get(military_info_army_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(army_dict.get(military_info_army_combat_key, false)) + + # unit_entry Panel + # - military_unit_entry_bg GUIIconButton + # - unit_progress GUIProgressBar (used when building units) + # - leader and unit_strip GUIIcons (unit_strip used when building units) + # - name, location, unit_eta, regiments, men GUILabels + # - military_cancel_unit GUIIconButton + # - morale_progress, strength_progress GUIProgressBars + # - moving, digin, combat GUIIcons + + const military_info_brigade_progress_key : StringName = &"brigade_progress" + const military_info_brigade_icon_key : StringName = &"brigade_icon" + const military_info_brigade_name_key : StringName = &"brigade_name" + const military_info_brigade_location_key : StringName = &"brigade_location" + const military_info_brigade_eta_key : StringName = &"brigade_eta" + # "Gathering the following goods before construction can begin: ..." - applied to most sub-nodes + const military_info_brigade_tooltip_key : StringName = &"brigade_tooltip" + + for index : int in clampi(_army_listbox.get_child_count() - armies.size(), 0, in_progress_brigades.size()): + var entry_menu : Panel = GUINode.get_panel_from_node(_army_listbox.get_child(index + armies.size())) + var brigade_dict : Dictionary = in_progress_brigades[index] + var brigade_tooltip : String = brigade_dict.get(military_info_brigade_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(brigade_dict.get(military_info_brigade_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(brigade_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(brigade_dict.get(military_info_brigade_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(brigade_dict.get(military_info_brigade_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(brigade_dict.get(military_info_brigade_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(brigade_dict.get(military_info_brigade_eta_key, "")) + var regiments_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) + if regiments_label: + regiments_label.hide() + 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 BRIGADE 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() + 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() + + + + + + + + +# TODO +# - use same code/dictionary keys to generate in-progress constructions for LAND and NAVAL +# - maybe combine LAND and NAVAL unit group generation a bit (ofc with special cases for specifics like digin and different localisation) +# - implement sorting! +# - i could store nodes in lists to avoid `get_node`-ing them every update? +# - how to handle connections - maybe have a fixed Callable including the entry's index, which can be converted back into a leader/army/navy/in-progress construction at the C++ level?!?! + + + + + + + + + + + + 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]) + in_progress_ships.push_back( + { + &"brigade_progress": 0.75, + &"brigade_icon": 9, + &"brigade_name": "Test Ship", + &"brigade_location": "301", + &"brigade_eta": "2025.11.3", + &"brigade_tooltip": tr(&"GOODS_PROJECT_LACK_GOODS") + } + ) + 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: + var total_entry_count : int = navies.size() + in_progress_ships.size() + _navy_listbox.clear_children(total_entry_count) + while _navy_listbox.get_child_count() < total_entry_count: + var navy_entry : Panel = GUINode.generate_gui_element(_gui_file, "unit_entry") + if not navy_entry: + break + var men_label : GUILabel = GUINode.get_gui_label_from_node(navy_entry.get_node(^"./men")) + if men_label: + men_label.hide() + var digin_icon : GUIIcon = GUINode.get_gui_icon_from_node(navy_entry.get_node(^"./digin")) + if digin_icon: + digin_icon.hide() + _navy_listbox.add_child(navy_entry) + + const military_info_navy_leader_picture_key : StringName = &"navy_leader_picture" + const military_info_navy_leader_tooltip_key : StringName = &"navy_leader_tooltip" + const military_info_navy_name_key : StringName = &"navy_name" + const military_info_navy_location_key : StringName = &"navy_location" + const military_info_navy_ship_count_key : StringName = &"navy_ship_count" + const military_info_navy_morale_key : StringName = &"navy_morale" + const military_info_navy_strength_key : StringName = &"navy_strength" + const military_info_navy_moving_tooltip_key : StringName = &"navy_moving_tooltip" + const military_info_navy_combat_key : StringName = &"navy_combat" + + for index : int in mini(navies.size(), _navy_listbox.get_child_count()): + var entry_menu : Panel = GUINode.get_panel_from_node(_navy_listbox.get_child(index)) + var navy_dict : Dictionary = navies[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 NAVY")) + 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 = navy_dict.get(military_info_navy_leader_picture_key, null) + if leader_texture: + leader_icon.show() + leader_icon.set_texture(leader_texture) + leader_icon.set_tooltip_string(navy_dict.get(military_info_navy_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(navy_dict.get(military_info_navy_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(navy_dict.get(military_info_navy_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 ships_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) + if ships_label: + ships_label.show() + var ship_count_string : String = str(navy_dict.get(military_info_navy_ship_count_key, 0)) + ships_label.set_text(ship_count_string) + ships_label.set_tooltip_string(tr(&"MILITARY_SHIPS_TOOLTIP").replace("$VALUE$", ship_count_string)) + 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 = navy_dict.get(military_info_navy_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() + var strength : float = navy_dict.get(military_info_navy_strength_key, 0) + strength_progress_bar.set_value_no_signal(strength) + strength_progress_bar.set_tooltip_string(tr(&"MILITARY_SHIPSTRENGTH_TOOLTIP2").replace("$PERCENT$", str(int(strength * 100)))) + var moving_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./moving")) + if moving_icon: + var moving_tooltip : String = navy_dict.get(military_info_navy_moving_tooltip_key, "") + moving_icon.set_visible(not moving_tooltip.is_empty()) + moving_icon.set_tooltip_string(moving_tooltip) + var combat_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./combat")) + if combat_icon: + combat_icon.set_visible(navy_dict.get(military_info_navy_combat_key, false)) + + # unit_entry Panel + # - military_unit_entry_bg GUIIconButton + # - unit_progress GUIProgressBar (used when building units) + # - leader and unit_strip GUIIcons (unit_strip used when building units) + # - name, location, unit_eta, regiments, men GUILabels + # - military_cancel_unit GUIIconButton + # - morale_progress, strength_progress GUIProgressBars + # - moving, digin, combat GUIIcons + + const military_info_brigade_progress_key : StringName = &"brigade_progress" + const military_info_brigade_icon_key : StringName = &"brigade_icon" + const military_info_brigade_name_key : StringName = &"brigade_name" + const military_info_brigade_location_key : StringName = &"brigade_location" + const military_info_brigade_eta_key : StringName = &"brigade_eta" + # "Gathering the following goods before construction can begin: ..." - applied to most sub-nodes + const military_info_brigade_tooltip_key : StringName = &"brigade_tooltip" + + for index : int in clampi(_navy_listbox.get_child_count() - navies.size(), 0, in_progress_ships.size()): + var entry_menu : Panel = GUINode.get_panel_from_node(_navy_listbox.get_child(index + navies.size())) + var brigade_dict : Dictionary = in_progress_ships[index] + var brigade_tooltip : String = brigade_dict.get(military_info_brigade_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(brigade_dict.get(military_info_brigade_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(brigade_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(brigade_dict.get(military_info_brigade_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(brigade_dict.get(military_info_brigade_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(brigade_dict.get(military_info_brigade_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(brigade_dict.get(military_info_brigade_eta_key, "")) + var ships_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) + if ships_label: + ships_label.hide() + 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 BRIGADE 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() + var combat_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./combat")) + if combat_icon: + combat_icon.hide() + 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: diff --git a/game/src/Game/MusicConductor/MusicConductor.gd b/game/src/Game/MusicConductor/MusicConductor.gd index ade8fb4d..e834dede 100644 --- a/game/src/Game/MusicConductor/MusicConductor.gd +++ b/game/src/Game/MusicConductor/MusicConductor.gd @@ -67,7 +67,7 @@ func toggle_play_pause() -> void: func start_current_song() -> void: _audio_stream_player.stream = _available_songs[_selected_track].song_stream - _audio_stream_player.play() + #_audio_stream_player.play() song_started.emit(_selected_track) # REQUIREMENTS