diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation index 859723ce..51e83e38 160000 --- a/extension/deps/openvic-simulation +++ b/extension/deps/openvic-simulation @@ -1 +1 @@ -Subproject commit 859723ced0444339ae3724b2bbfc0a7e99f7990a +Subproject commit 51e83e38986217aa6b5f5a5a1d714aa13a0bff64 diff --git a/extension/src/openvic-extension/classes/GUIListBox.cpp b/extension/src/openvic-extension/classes/GUIListBox.cpp index 958807fd..39ec9746 100644 --- a/extension/src/openvic-extension/classes/GUIListBox.cpp +++ b/extension/src/openvic-extension/classes/GUIListBox.cpp @@ -270,7 +270,7 @@ Error GUIListBox::set_gui_listbox(GUI::ListBox const* new_gui_listbox) { if (scrollbar_control != nullptr) { scrollbar = Object::cast_to(scrollbar_control); if (scrollbar != nullptr) { - add_child(scrollbar, false, INTERNAL_MODE_FRONT); + add_child(scrollbar, false, INTERNAL_MODE_BACK); const Size2 size = Utilities::to_godot_fvec2(gui_listbox->get_size()); Vector2 position = Utilities::to_godot_fvec2(gui_listbox->get_scrollbar_offset()); 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..9cd87007 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.cpp +++ b/extension/src/openvic-extension/singletons/AssetManager.cpp @@ -14,6 +14,7 @@ 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" }); BIND_ENUM_CONSTANT(LOAD_FLAG_NONE); BIND_ENUM_CONSTANT(LOAD_FLAG_CACHE_IMAGE); @@ -195,6 +196,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 +217,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 +233,7 @@ Ref AssetManager::get_currency_texture(real_t height) const { return currency_texture_small; } } + +Ref AssetManager::get_leader_texture(std::string_view name) { + return get_texture(Utilities::std_to_godot_string(CultureManager::make_leader_picture_path(name))); +} diff --git a/extension/src/openvic-extension/singletons/AssetManager.hpp b/extension/src/openvic-extension/singletons/AssetManager.hpp index 14e04e24..c769b644 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.hpp +++ b/extension/src/openvic-extension/singletons/AssetManager.hpp @@ -83,11 +83,15 @@ 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::string_view name); }; } diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp index 75d03e5a..6bb8b135 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" @@ -51,6 +52,7 @@ String MenuSingleton::get_state_name(State const& state) const { if (!named) { // Capital province name + // TODO - confirm capital is never null? name = tr(GUINode::format_province_name(Utilities::std_to_godot_string(state.get_capital()->get_identifier()))); if (!owned) { @@ -117,18 +119,13 @@ String MenuSingleton::make_modifier_effects_tooltip(ModifierValue const& modifie String result; for (auto const& [effect, value] : modifier.get_values()) { - if (!result.is_empty()) { - result += "\n"; - } - - result += tr(Utilities::std_to_godot_string(effect->get_localisation_key())); - static const String post_name_text = ": " + GUILabel::get_colour_marker(); - result += post_name_text; + + result += "\n" + tr(Utilities::std_to_godot_string(effect->get_localisation_key())) + post_name_text; if (value == 0) { result += "Y"; - } else if (effect->is_positive_good() == value > 0) { + } else if (effect->is_positive_good() == (value > 0)) { result += "G"; } else { result += "R"; @@ -183,11 +180,7 @@ String MenuSingleton::make_rules_tooltip(RuleSet const& rules) const { for (auto const& [rule_group, rule_map] : rules.get_rule_groups()) { for (auto const& [rule, enabled] : rule_map) { - if (!result.is_empty()) { - result += "\n"; - } - - result += tr(Utilities::std_to_godot_string(rule->get_localisation_key())) + result += "\n" + tr(Utilities::std_to_godot_string(rule->get_localisation_key())) + (enabled ? enabled_text : disabled_text); } } @@ -286,6 +279,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" }); @@ -730,11 +733,15 @@ Dictionary MenuSingleton::get_topbar_info() const { { String military_power_tooltip; + static const StringName military_power_from_land_key = "MIL_FROM_TROOPS"; + static const StringName military_power_from_sea_key = "MIL_FROM_CAP_SHIPS"; + static const StringName military_power_from_leaders_key = "MIL_FROM_LEADERS"; + for (auto const& [source, power] : { std::pair - { "MIL_FROM_TROOPS", country->get_military_power_from_land() }, - { "MIL_FROM_CAP_SHIPS", country->get_military_power_from_sea() }, - { "MIL_FROM_LEADERS", country->get_military_power_from_leaders() } + { military_power_from_land_key, country->get_military_power_from_land() }, + { military_power_from_sea_key, country->get_military_power_from_sea() }, + { military_power_from_leaders_key, country->get_military_power_from_leaders() } }) { if (power != 0) { military_power_tooltip += "\n" + tr(source) + ": " + GUILabel::get_colour_marker() + "Y" @@ -876,6 +883,537 @@ String MenuSingleton::get_longform_date() const { return Utilities::date_to_formatted_string(instance_manager->get_today()); } +/* 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()) { + Ref texture = asset_manager->get_leader_texture(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())); + // Add in this order to get the same tooltip order as the base + modifier_value = *leader.get_background() + modifier_value; + } else { + static const StringName missing_background = "no_background"; + background = tr(missing_background); + } + + static const StringName background_localisation_key = "MILITARY_BACKGROUND"; + static const String background_localisation_replace_key = "$NAME$"; + + tooltip += "\n" + tr(background_localisation_key).replace( + background_localisation_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_localisation_replace_key = "$NAME$"; + + tooltip += "\n" + tr(personality_localisation_key).replace( + personality_localisation_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 StringName moving_location_replace_key = "$LOCATION$"; + static const StringName 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 StringName 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] = 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 StringName moving_location_replace_key = "$LOCATION$"; + static const StringName 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, {}); + + 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_economy_impact_key = "mobilisation_economy_impact"; + + 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()); + ret[military_info_mobilisation_economy_impact_key] = country->get_mobilisation_economy_impact().to_float(); + + // 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() { GameSingleton const* game_singleton = GameSingleton::get_singleton(); ERR_FAIL_NULL_V(game_singleton, FAILED); diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.hpp b/extension/src/openvic-extension/singletons/MenuSingleton.hpp index 0d38b649..9c6e1b3c 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ namespace OpenVic { struct RebelType; struct ModifierValue; struct RuleSet; + struct LeaderBase; class MenuSingleton : public godot::Object { GDCLASS(MenuSingleton, godot::Object) @@ -93,6 +95,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; @@ -124,6 +132,8 @@ namespace OpenVic { 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; @@ -203,6 +213,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) @@ -216,3 +232,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/utility/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp index 1fcdea8d..4e562d85 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 c1739388..e5658df8 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/MapView.gd b/game/src/Game/GameSession/MapView.gd index 1d837f85..6ebdefd4 100644 --- a/game/src/Game/GameSession/MapView.gd +++ b/game/src/Game/GameSession/MapView.gd @@ -34,7 +34,7 @@ var _mouse_over_viewport : bool = true # hence why it is not exported and just has _zoom_target_max as a placeholder. var _zoom_target : float = _zoom_target_max: get: return _zoom_target - set(v): _zoom_target = clamp(v, _zoom_target_min, _zoom_target_max) + set(v): _zoom_target = clampf(v, _zoom_target_min, _zoom_target_max) const _zoom_position_multiplier = 3.14159 # Horizontal movement coefficient during zoom var _zoom_position : Vector2 @@ -293,7 +293,7 @@ func _edge_scrolling_vector() -> Vector2: func _clamp_over_map() -> void: _camera.position.x = _map_mesh_corner.x + fposmod(_camera.position.x - _map_mesh_corner.x, _map_mesh_dims.x) - _camera.position.z = clamp(_camera.position.z, _map_mesh_corner.y, _map_mesh_corner.y + _map_mesh_dims.y) + _camera.position.z = clampf(_camera.position.z, _map_mesh_corner.y, _map_mesh_corner.y + _map_mesh_dims.y) func _update_view_states(force_signal : bool) -> void: var new_is_parchment_view : bool = _camera.position.y >= _zoom_parchment_threshold - _zoom_epsilon diff --git a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd index 10446e3c..87b63ab0 100644 --- a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd @@ -43,11 +43,10 @@ var _debt_chart : GUIPieChart const _screen : NationManagement.Screen = NationManagement.Screen.BUDGET # TODO - testing function, should be replaced with calls to SIM which trigger UI updates through gamestate_updated -func _on_tax_slider_changed(slider : GUIScrollbar, label : GUILabel, tooltip : String, value : int) -> void: +func _on_tax_slider_changed(slider : GUIScrollbar, label : GUILabel, tooltip : StringName, value : int) -> void: label.text = "%s¤" % GUINode.float_to_string_dp(value, 3 if abs(value) < 1000 else 1) slider.set_tooltip_string("%s: §Y%s%%" % [tr(tooltip), GUINode.float_to_string_dp(value, 1)]) - func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -97,21 +96,21 @@ func _ready() -> void: if _lower_class_slider and _lower_class_label: _lower_class_slider.value_changed.connect( func (value : int) -> void: - _on_tax_slider_changed(_lower_class_slider, _lower_class_label, "BUDGET_TAX_POOR", value) + _on_tax_slider_changed(_lower_class_slider, _lower_class_label, &"BUDGET_TAX_POOR", value) ) _lower_class_slider.emit_value_changed() var _middle_class_slider : GUIScrollbar = get_gui_scrollbar_from_nodepath(^"./country_budget/tax_1_slider") if _middle_class_slider and _middle_class_label: _middle_class_slider.value_changed.connect( func (value : int) -> void: - _on_tax_slider_changed(_middle_class_slider, _middle_class_label, "BUDGET_TAX_MIDDLE", value) + _on_tax_slider_changed(_middle_class_slider, _middle_class_label, &"BUDGET_TAX_MIDDLE", value) ) _middle_class_slider.emit_value_changed() var _upper_class_slider : GUIScrollbar = get_gui_scrollbar_from_nodepath(^"./country_budget/tax_2_slider") if _upper_class_slider and _upper_class_label: _upper_class_slider.value_changed.connect( func (value : int) -> void: - _on_tax_slider_changed(_upper_class_slider, _upper_class_label, "BUDGET_TAX_RICH", value) + _on_tax_slider_changed(_upper_class_slider, _upper_class_label, &"BUDGET_TAX_RICH", value) ) _upper_class_slider.emit_value_changed() diff --git a/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd b/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd index 11d40d15..035a495d 100644 --- a/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd @@ -4,19 +4,232 @@ 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")) + _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")) + sort_armies_name_button.pressed.connect(func() -> void: print("SORT ARMIES BY NAME")) + var sort_armies_strength_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./sort_strength")) + sort_armies_strength_button.pressed.connect(func() -> void: print("SORT ARMIES BY STRENGTH")) + + _build_army_button = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./build_new")) + if _build_army_button: + _build_army_button.set_text("MILITARY_BUILD_ARMY") + _build_army_button.pressed.connect(func() -> void: "BUILD ARMY") + + _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")) + sort_navies_name_button.pressed.connect(func() -> void: print("SORT NAVIES BY NAME")) + var sort_navies_strength_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./sort_strength")) + sort_navies_strength_button.pressed.connect(func() -> void: print("SORT NAVIES BY STRENGTH")) + + _build_navy_button = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./build_new")) + if _build_navy_button: + _build_navy_button.set_text("MILITARY_BUILD_NAVY") + _build_navy_button.pressed.connect(func() -> void: "BUILD NAVY") + + _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 +243,600 @@ 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_economy_impact_key : StringName = &"mobilisation_economy_impact" + + # 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) + if _mobilise_button: + _mobilise_button.set_visible(not is_mobilised) + if _demobilise_button: + _demobilise_button.set_visible(is_mobilised) + var mobilisation_progress : float = military_info.get(military_info_mobilisation_progress_key, 0) + if _mobilisation_progress_bar: + _mobilisation_progress_bar.set_value_no_signal(mobilisation_progress) + if _mobilisation_progress_label: + _mobilisation_progress_label.set_text("%s%%" % GUINode.float_to_string_dp(100 * mobilisation_progress, 2)) + if _mobilisation_size_label: + _mobilisation_size_label.set_text(str(military_info.get(military_info_mobilisation_size_key, 0))) + 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)) + + # 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.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: + _army_count_label.set_text(str(armies.size())) + if _in_progress_brigade_count_label: + _in_progress_brigade_count_label.set_text("(+%d)" % in_progress_brigades.size()) + 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: + leader_icon.show() + var leader_texture : Texture2D = army_dict.get(military_info_army_leader_picture_key, null) + if leader_texture: + leader_icon.set_texture(leader_texture) + leader_icon.set_tooltip_string(army_dict.get(military_info_army_leader_tooltip_key, "")) + 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: + _navy_count_label.set_text(str(navies.size())) + if _in_progress_ship_count_label: + _in_progress_ship_count_label.set_text("(+%d)" % in_progress_ships.size()) + 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: + leader_icon.show() + var leader_texture : Texture2D = navy_dict.get(military_info_navy_leader_picture_key, null) + if leader_texture: + leader_icon.set_texture(leader_texture) + leader_icon.set_tooltip_string(navy_dict.get(military_info_navy_leader_tooltip_key, "")) + 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/NationManagementScreen/PopulationMenu.gd b/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd index b872db0f..e9c151f8 100644 --- a/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd @@ -600,7 +600,7 @@ func _update_pop_list() -> void: var unemployment : float = pop_row[pop_unemployment_key] _pop_list_unemployment_progressbars[index].set_value_no_signal(unemployment) _pop_list_unemployment_progressbars[index].set_tooltip_string("%s: §Y%s%%" % [ - tr("UNEMPLOYMENT"), GUINode.float_to_string_dp(unemployment * 100.0, 3) + tr(&"UNEMPLOYMENT"), GUINode.float_to_string_dp(unemployment * 100.0, 3) ]) if _pop_list_cash_labels[index]: _pop_list_cash_labels[index].set_text("%s¤" % GUINode.float_to_string_dp(pop_row[pop_cash_key], 2)) @@ -645,12 +645,12 @@ func _update_pop_list() -> void: var pop_change : int = pop_row[pop_size_change_key] _pop_list_size_change_icons[index].set_icon_index(get_growth_icon_index(pop_change)) _pop_list_size_change_icons[index].set_tooltip_string("%s §%s%s" % [ - tr("POPULATION_CHANGED_BY"), "G+" if pop_change > 0 else "Y+" if pop_change == 0 else "R", str(pop_change) + tr(&"POPULATION_CHANGED_BY"), "G+" if pop_change > 0 else "Y+" if pop_change == 0 else "R", str(pop_change) ]) if _pop_list_literacy_labels[index]: _pop_list_literacy_labels[index].set_text("%s%%" % GUINode.float_to_string_dp(pop_row[pop_literacy_key], 2)) _pop_list_literacy_labels[index].set_tooltip_string("%s: §G%s%%" % [ - tr("LIT_CHANGE"), GUINode.float_to_string_dp(pop_row[pop_literacy_key] / 64.0, 2) + tr(&"LIT_CHANGE"), GUINode.float_to_string_dp(pop_row[pop_literacy_key] / 64.0, 2) ]) _pop_list_rows[index].show() diff --git a/game/src/Game/GameSession/Topbar.gd b/game/src/Game/GameSession/Topbar.gd index 11e584cd..4f917d29 100644 --- a/game/src/Game/GameSession/Topbar.gd +++ b/game/src/Game/GameSession/Topbar.gd @@ -153,7 +153,7 @@ func _ready() -> void: Events.NationManagementScreens.toggle_nation_management_screen.bind(screen) ) # TODO - test tooltip, replace with actual shortcut strings - button.set_tooltip_string(tr("SHORTCUT") + "F3") + button.set_tooltip_string(tr(&"SHORTCUT") + "F3") _nation_management_buttons[screen] = button Events.NationManagementScreens.update_active_nation_management_screen.connect( _on_update_active_nation_management_screen @@ -311,7 +311,7 @@ func _update_info() -> void: var country_name : String = MenuSingleton.get_country_name_from_identifier(country_identifier) var country_status : int = topbar_info.get(country_status_key, CountryStatus.UNCIVILISED) - var country_name_rank_tooltip : String = tr("PLAYER_COUNTRY_TOPBAR_RANK") + MenuSingleton.get_tooltip_separator() + tr("RANK_TOTAL_D") + var country_name_rank_tooltip : String = tr(&"PLAYER_COUNTRY_TOPBAR_RANK") + MenuSingleton.get_tooltip_separator() + tr(&"RANK_TOTAL_D") var country_name_rank_dict : Dictionary = { "NAME": country_name, "RANK": COUNTRY_STATUS_NAMES[country_status] @@ -335,7 +335,7 @@ func _update_info() -> void: _country_rank_label.set_text(str(topbar_info.get(total_rank_key, 0))) _country_rank_label.set_tooltip_string_and_substitution_dict(country_name_rank_tooltip, country_name_rank_dict) - var prestige_tooltip : String = tr("RANK_PRESTIGE") + topbar_info.get(prestige_tooltip_key, "") + MenuSingleton.get_tooltip_separator() + tr("RANK_PRESTIGE_D") + var prestige_tooltip : String = tr(&"RANK_PRESTIGE") + topbar_info.get(prestige_tooltip_key, "") + MenuSingleton.get_tooltip_separator() + tr(&"RANK_PRESTIGE_D") if _country_prestige_label: _country_prestige_label.set_text(str(topbar_info.get(prestige_key, 0))) @@ -345,7 +345,7 @@ func _update_info() -> void: _country_prestige_rank_label.set_text(str(topbar_info.get(prestige_rank_key, 0))) _country_prestige_rank_label.set_tooltip_string(prestige_tooltip) - var industrial_power_tooltip : String = tr("RANK_INDUSTRY") + MenuSingleton.get_tooltip_separator() + tr("RANK_INDUSTRY_D") + topbar_info.get(industrial_power_tooltip_key, "") + var industrial_power_tooltip : String = tr(&"RANK_INDUSTRY") + MenuSingleton.get_tooltip_separator() + tr(&"RANK_INDUSTRY_D") + topbar_info.get(industrial_power_tooltip_key, "") if _country_industrial_power_label: _country_industrial_power_label.set_text(str(topbar_info.get(industrial_power_key, 0))) @@ -355,7 +355,7 @@ func _update_info() -> void: _country_industrial_power_rank_label.set_text(str(topbar_info.get(industrial_rank_key, 0))) _country_industrial_power_rank_label.set_tooltip_string(industrial_power_tooltip) - var military_power_tooltip : String = tr("RANK_MILITARY") + MenuSingleton.get_tooltip_separator() + tr("RANK_MILITARY_D") + topbar_info.get(military_power_tooltip_key, "") + var military_power_tooltip : String = tr(&"RANK_MILITARY") + MenuSingleton.get_tooltip_separator() + tr(&"RANK_MILITARY_D") + topbar_info.get(military_power_tooltip_key, "") if _country_military_power_label: _country_military_power_label.set_text(str(topbar_info.get(military_power_key, 0))) @@ -371,8 +371,8 @@ func _update_info() -> void: _country_colonial_power_label.set_text( "§%s%s§!/%s" % ["W" if available_colonial_power > 0 else "R", available_colonial_power, max_colonial_power] ) - _country_colonial_power_label.set_tooltip_string(tr("COLONIAL_POINTS") + MenuSingleton.get_tooltip_separator() + ( - topbar_info.get(colonial_power_tooltip_key, "") if country_status <= CountryStatus.SECONDARY_POWER else tr("NON_COLONIAL_POWER") + _country_colonial_power_label.set_tooltip_string(tr(&"COLONIAL_POINTS") + MenuSingleton.get_tooltip_separator() + ( + topbar_info.get(colonial_power_tooltip_key, "") if country_status <= CountryStatus.SECONDARY_POWER else tr(&"NON_COLONIAL_POWER") )) ## Time control @@ -536,7 +536,7 @@ func _update_info() -> void: _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$"), + 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