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