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