From c79bf36ced0319572c561b82a2a9158ffa3bc94b Mon Sep 17 00:00:00 2001 From: hop311 Date: Wed, 20 Mar 2024 22:51:08 +0000 Subject: Implement Population Menu --- .../openvic-extension/singletons/GameSingleton.cpp | 7 + .../openvic-extension/singletons/MenuSingleton.cpp | 69 +- .../openvic-extension/singletons/MenuSingleton.hpp | 96 +++ .../singletons/PopulationMenu.cpp | 712 +++++++++++++++++++++ 4 files changed, 882 insertions(+), 2 deletions(-) create mode 100644 extension/src/openvic-extension/singletons/PopulationMenu.cpp (limited to 'extension/src/openvic-extension') diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp index 459b2c8..563f1cb 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -9,6 +9,7 @@ #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/LoadLocalisation.hpp" +#include "openvic-extension/singletons/MenuSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" #include "openvic-extension/utility/Utilities.hpp" @@ -115,6 +116,7 @@ Error GameSingleton::setup_game(int32_t bookmark_index) { Bookmark const* bookmark = game_manager.get_history_manager().get_bookmark_manager().get_bookmark_by_index(bookmark_index); ERR_FAIL_NULL_V_MSG(bookmark, FAILED, vformat("Failed to get bookmark with index: %d", bookmark_index)); bool ret = game_manager.load_bookmark(bookmark); + for (Province& province : game_manager.get_map().get_provinces()) { province.set_crime( game_manager.get_crime_manager().get_crime_modifier_by_index( @@ -122,6 +124,11 @@ Error GameSingleton::setup_game(int32_t bookmark_index) { ) ); } + + MenuSingleton* menu_singleton = MenuSingleton::get_singleton(); + ERR_FAIL_NULL_V(menu_singleton, FAILED); + menu_singleton->_population_menu_update_provinces(); + return ERR(ret); } diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp index 52fb6af..02fcd54 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.cpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.cpp @@ -1,7 +1,5 @@ #include "MenuSingleton.hpp" -#include - #include #include "openvic-extension/classes/GFXPieChartTexture.hpp" @@ -15,6 +13,19 @@ using namespace OpenVic; using OpenVic::Utilities::std_to_godot_string; using OpenVic::Utilities::std_view_to_godot_string; +StringName const& MenuSingleton::_signal_population_menu_province_list_changed() { + static const StringName signal_population_menu_province_list_changed = "population_menu_province_list_changed"; + return signal_population_menu_province_list_changed; +} +StringName const& MenuSingleton::_signal_population_menu_province_list_selected_changed() { + static const StringName signal_population_menu_province_list_selected_changed = "population_menu_province_list_selected_changed"; + return signal_population_menu_province_list_selected_changed; +} +StringName const& MenuSingleton::_signal_population_menu_pops_changed() { + static const StringName signal_population_menu_pops_changed = "population_menu_pops_changed"; + return signal_population_menu_pops_changed; +} + void MenuSingleton::_bind_methods() { /* PROVINCE OVERVIEW PANEL */ OV_BIND_METHOD(MenuSingleton::get_province_info_from_index, { "index" }); @@ -35,6 +46,60 @@ void MenuSingleton::_bind_methods() { OV_BIND_METHOD(MenuSingleton::can_increase_speed); OV_BIND_METHOD(MenuSingleton::can_decrease_speed); OV_BIND_METHOD(MenuSingleton::get_longform_date); + + /* POPULATION MENU */ + OV_BIND_METHOD(MenuSingleton::get_population_menu_province_list_row_count); + OV_BIND_METHOD(MenuSingleton::get_population_menu_province_list_rows, { "start", "count" }); + OV_BIND_METHOD( + MenuSingleton::population_menu_select_province_list_entry, { "select_index", "set_scroll_index" }, DEFVAL(false) + ); + OV_BIND_METHOD(MenuSingleton::population_menu_select_province, { "province_index" }); + OV_BIND_METHOD(MenuSingleton::population_menu_toggle_expanded, { "toggle_index", "emit_selected_changed" }, DEFVAL(true)); + + OV_BIND_METHOD(MenuSingleton::population_menu_select_sort_key, { "sort_key" }); + OV_BIND_METHOD(MenuSingleton::get_population_menu_pop_rows, { "start", "count" }); + OV_BIND_METHOD(MenuSingleton::get_population_menu_pop_row_count); + + OV_BIND_METHOD(MenuSingleton::get_population_menu_pop_filter_setup_info); + OV_BIND_METHOD(MenuSingleton::get_population_menu_pop_filter_info); + OV_BIND_METHOD(MenuSingleton::population_menu_toggle_pop_filter, { "filter_index" }); + OV_BIND_METHOD(MenuSingleton::population_menu_select_all_pop_filters); + OV_BIND_METHOD(MenuSingleton::population_menu_deselect_all_pop_filters); + + OV_BIND_METHOD(MenuSingleton::get_population_menu_distribution_setup_info); + OV_BIND_METHOD(MenuSingleton::get_population_menu_distribution_info); + + ADD_SIGNAL(MethodInfo(_signal_population_menu_province_list_changed())); + ADD_SIGNAL( + MethodInfo(_signal_population_menu_province_list_selected_changed(), PropertyInfo(Variant::INT, "scroll_index")) + ); + ADD_SIGNAL(MethodInfo(_signal_population_menu_pops_changed())); + + using enum population_menu_t::ProvinceListEntry; + BIND_ENUM_CONSTANT(LIST_ENTRY_NONE); + BIND_ENUM_CONSTANT(LIST_ENTRY_COUNTRY); + BIND_ENUM_CONSTANT(LIST_ENTRY_STATE); + BIND_ENUM_CONSTANT(LIST_ENTRY_PROVINCE); + + using enum population_menu_t::PopSortKey; + BIND_ENUM_CONSTANT(NONE); + BIND_ENUM_CONSTANT(SORT_SIZE); + BIND_ENUM_CONSTANT(SORT_TYPE); + BIND_ENUM_CONSTANT(SORT_CULTURE); + BIND_ENUM_CONSTANT(SORT_RELIGION); + BIND_ENUM_CONSTANT(SORT_LOCATION); + BIND_ENUM_CONSTANT(SORT_MILITANCY); + BIND_ENUM_CONSTANT(SORT_CONSCIOUSNESS); + BIND_ENUM_CONSTANT(SORT_IDEOLOGY); + BIND_ENUM_CONSTANT(SORT_ISSUES); + BIND_ENUM_CONSTANT(SORT_UNEMPLOYMENT); + BIND_ENUM_CONSTANT(SORT_CASH); + BIND_ENUM_CONSTANT(SORT_LIFE_NEEDS); + BIND_ENUM_CONSTANT(SORT_EVERYDAY_NEEDS); + BIND_ENUM_CONSTANT(SORT_LUXURY_NEEDS); + BIND_ENUM_CONSTANT(SORT_REBEL_FACTION); + BIND_ENUM_CONSTANT(SORT_SIZE_CHANGE); + BIND_ENUM_CONSTANT(SORT_LITERACY); } MenuSingleton* MenuSingleton::get_singleton() { diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.hpp b/extension/src/openvic-extension/singletons/MenuSingleton.hpp index 6bd90c5..fd1b6c5 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp @@ -2,10 +2,12 @@ #include +#include #include namespace OpenVic { struct GameManager; + struct Region; class MenuSingleton : public godot::Object { GDCLASS(MenuSingleton, godot::Object) @@ -14,6 +16,70 @@ namespace OpenVic { GameManager* game_manager; + public: + struct population_menu_t { + enum ProvinceListEntry { + LIST_ENTRY_NONE, LIST_ENTRY_COUNTRY, LIST_ENTRY_STATE, LIST_ENTRY_PROVINCE + }; + + struct country_entry_t { + Country const& country; + bool selected = true; + }; + + struct state_entry_t { + // TODO - change to State + Region const& state; + bool selected = true, expanded = false; + }; + + struct province_entry_t { + Province const& province; + bool selected = true; + }; + + using province_list_entry_t = std::variant; + + std::vector province_list_entries; + int32_t visible_province_list_entries = 0; + + struct pop_filter_t { + Pop::pop_size_t count, promotion_demotion_change; + bool selected; + }; + ordered_map pop_filters; + + static constexpr int32_t DISTRIBUTION_COUNT = 6; + /* Distributions: + * - Workforce (PopType) + * - Religion + * - Ideology + * - Nationality (Culture) + * - Issues + * - Vote */ + std::array, DISTRIBUTION_COUNT> distributions; + + enum PopSortKey { + NONE, SORT_SIZE, SORT_TYPE, SORT_CULTURE, SORT_RELIGION, SORT_LOCATION, SORT_MILITANCY, SORT_CONSCIOUSNESS, + SORT_IDEOLOGY, SORT_ISSUES, SORT_UNEMPLOYMENT, SORT_CASH, SORT_LIFE_NEEDS, SORT_EVERYDAY_NEEDS, + SORT_LUXURY_NEEDS, SORT_REBEL_FACTION, SORT_SIZE_CHANGE, SORT_LITERACY, MAX_SORT_KEY + } sort_key = NONE; + bool sort_descending = true; + + std::vector pops, filtered_pops; + }; + + private: + population_menu_t population_menu; + + /* Emitted when the number of visible province list rows changes (list generated or state entry expanded).*/ + static godot::StringName const& _signal_population_menu_province_list_changed(); + /* Emitted when the state of visible province list rows changes (selection changes). Provides an integer argument + * which, if not negative, the province list scroll index should be updated to. */ + static godot::StringName const& _signal_population_menu_province_list_selected_changed(); + /* Emitted when the selected/filtered collection of pops changes. */ + static godot::StringName const& _signal_population_menu_pops_changed(); + protected: static void _bind_methods(); @@ -44,5 +110,35 @@ namespace OpenVic { bool can_increase_speed() const; bool can_decrease_speed() const; godot::String get_longform_date() const; + + /* POPULATION MENU */ + void _population_menu_update_provinces(); + int32_t get_population_menu_province_list_row_count() const; + godot::TypedArray get_population_menu_province_list_rows(int32_t start, int32_t count) const; + godot::Error population_menu_select_province_list_entry(int32_t select_index, bool set_scroll_index = false); + godot::Error population_menu_select_province(int32_t province_index); + godot::Error population_menu_toggle_expanded(int32_t toggle_index, bool emit_selected_changed = true); + + void _population_menu_update_pops(); + void _population_menu_update_filtered_pops(); + using sort_func_t = std::function; + sort_func_t _get_population_menu_sort_func(population_menu_t::PopSortKey sort_key) const; + void _population_menu_sort_pops(); + godot::Error population_menu_select_sort_key(population_menu_t::PopSortKey sort_key); + godot::TypedArray get_population_menu_pop_rows(int32_t start, int32_t count) const; + int32_t get_population_menu_pop_row_count() const; + + godot::PackedInt32Array get_population_menu_pop_filter_setup_info(); + godot::TypedArray get_population_menu_pop_filter_info() const; + godot::Error population_menu_toggle_pop_filter(int32_t filter_index); + void population_menu_select_all_pop_filters(); + void population_menu_deselect_all_pop_filters(); + + godot::PackedStringArray get_population_menu_distribution_setup_info() const; + /* Array of GFXPieChartTexture::godot_pie_chart_data_t. */ + godot::TypedArray get_population_menu_distribution_info() const; }; } + +VARIANT_ENUM_CAST(OpenVic::MenuSingleton::population_menu_t::ProvinceListEntry); +VARIANT_ENUM_CAST(OpenVic::MenuSingleton::population_menu_t::PopSortKey); diff --git a/extension/src/openvic-extension/singletons/PopulationMenu.cpp b/extension/src/openvic-extension/singletons/PopulationMenu.cpp new file mode 100644 index 0000000..b256e1f --- /dev/null +++ b/extension/src/openvic-extension/singletons/PopulationMenu.cpp @@ -0,0 +1,712 @@ +#include "MenuSingleton.hpp" + +#include + +#include + +#include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +using OpenVic::Utilities::std_view_to_godot_string; + +/* POPULATION MENU */ + +void MenuSingleton::_population_menu_update_provinces() { + ERR_FAIL_NULL(game_manager); + + population_menu.province_list_entries.clear(); + population_menu.visible_province_list_entries = 0; + + for (Country const* country : { + // Example country + game_manager->get_country_manager().get_country_by_identifier("ENG") + }) { + ERR_CONTINUE(country == nullptr); + + population_menu.province_list_entries.emplace_back(population_menu_t::country_entry_t { *country }); + population_menu.visible_province_list_entries++; + + // TODO - change to State + for (Region const& state : game_manager->get_map().get_regions()) { + + population_menu.province_list_entries.emplace_back(population_menu_t::state_entry_t { state }); + population_menu.visible_province_list_entries++; + + for (Province const* province : state.get_provinces()) { + population_menu.province_list_entries.emplace_back(population_menu_t::province_entry_t { *province }); + } + } + } + + population_menu.sort_key = population_menu_t::NONE; + + emit_signal(_signal_population_menu_province_list_changed()); + + // TODO - may need to emit population_menu_province_list_selected_changed if _update_info cannot be guaranteed + + _population_menu_update_pops(); +} + +int32_t MenuSingleton::get_population_menu_province_list_row_count() const { + return population_menu.visible_province_list_entries; +} + +TypedArray MenuSingleton::get_population_menu_province_list_rows(int32_t start, int32_t count) const { + if (population_menu.province_list_entries.empty()) { + return {}; + } + + ERR_FAIL_INDEX_V_MSG( + start, population_menu.visible_province_list_entries, {}, + vformat("Invalid start for population menu province list rows: %d", start) + ); + ERR_FAIL_COND_V_MSG(count <= 0, {}, vformat("Invalid count for population menu province list rows: %d", count)); + + static const StringName type_key = "type"; + static const StringName index_key = "index"; + static const StringName name_key = "name"; + static const StringName size_key = "size"; + static const StringName change_key = "change"; + static const StringName selected_key = "selected"; + /* State-only keys */ + static const StringName expanded_key = "expanded"; + static const StringName colonial_status_key = "colony"; + // TODO - national focus + + struct entry_visitor_t { + + int32_t& start_counter; + int32_t& count_counter; + + /* This is the index among all entries, not just visible ones unlike start and count. */ + int32_t index = 0; + + bool is_expanded = true; + + TypedArray array {}; + + /* Overloads return false if count_counter reaches 0 and the function should return, + * otherwise true indicating the province list loop should continue. */ + + bool operator()(population_menu_t::country_entry_t const& country_entry) { + if (start_counter-- <= 0) { + Dictionary country_dict; + + country_dict[type_key] = population_menu_t::LIST_ENTRY_COUNTRY; + country_dict[index_key] = index; + country_dict[name_key] = std_view_to_godot_string(country_entry.country.get_identifier()); + country_dict[size_key] = 0; + country_dict[change_key] = 0; + country_dict[selected_key] = country_entry.selected; + + array.push_back(country_dict); + + return --count_counter > 0; + } + + return true; + } + + bool operator()(population_menu_t::state_entry_t const& state_entry) { + is_expanded = state_entry.expanded; + + if (start_counter-- <= 0) { + Dictionary state_dict; + + state_dict[type_key] = population_menu_t::LIST_ENTRY_STATE; + state_dict[index_key] = index; + state_dict[name_key] = std_view_to_godot_string(state_entry.state.get_identifier()); + state_dict[size_key] = 0; + state_dict[change_key] = 0; + state_dict[selected_key] = state_entry.selected; + state_dict[expanded_key] = state_entry.expanded; + state_dict[colonial_status_key] = false; + + array.push_back(state_dict); + + return --count_counter > 0; + } + + return true; + } + + bool operator()(population_menu_t::province_entry_t const& province_entry) { + if (is_expanded && start_counter-- <= 0) { + Dictionary province_dict; + + province_dict[type_key] = population_menu_t::LIST_ENTRY_PROVINCE; + province_dict[index_key] = index; + province_dict[name_key] = std_view_to_godot_string(province_entry.province.get_identifier()); + province_dict[size_key] = province_entry.province.get_total_population(); + province_dict[change_key] = 0; + province_dict[selected_key] = province_entry.selected; + + array.push_back(province_dict); + + return --count_counter > 0; + } + + return true; + } + } entry_visitor { start, count }; + + while (entry_visitor.index < population_menu.province_list_entries.size() + && std::visit(entry_visitor, population_menu.province_list_entries[entry_visitor.index])) { + entry_visitor.index++; + } + + return entry_visitor.array; +} + +Error MenuSingleton::population_menu_select_province_list_entry(int32_t select_index, bool set_scroll_index) { + ERR_FAIL_INDEX_V(select_index, population_menu.province_list_entries.size(), FAILED); + + struct entry_visitor { + + const int32_t _select_index; + + int32_t index = 0, visible_index = 0; + bool is_expanded = true; + + int32_t selected_visible_index = -1; + + using enum population_menu_t::ProvinceListEntry; + population_menu_t::ProvinceListEntry select_level = LIST_ENTRY_NONE; + + void operator()(population_menu_t::country_entry_t& country_entry) { + if (index == _select_index) { + select_level = LIST_ENTRY_COUNTRY; + + country_entry.selected = true; + + selected_visible_index = visible_index; + } else { + select_level = LIST_ENTRY_NONE; + + country_entry.selected = false; + } + + visible_index++; + } + + void operator()(population_menu_t::state_entry_t& state_entry) { + if (select_level == LIST_ENTRY_COUNTRY) { + state_entry.selected = true; + } else if (index == _select_index) { + select_level = LIST_ENTRY_STATE; + + state_entry.selected = true; + + selected_visible_index = visible_index; + } else { + select_level = LIST_ENTRY_NONE; + state_entry.selected = false; + } + + visible_index++; + + is_expanded = state_entry.expanded; + } + + void operator()(population_menu_t::province_entry_t& province_entry) { + if (select_level == LIST_ENTRY_COUNTRY || select_level == LIST_ENTRY_STATE) { + province_entry.selected = true; + } else if (index == _select_index) { + province_entry.selected = true; + + selected_visible_index = visible_index; + } else { + province_entry.selected = false; + } + + if (is_expanded) { + visible_index++; + } + } + + } entry_visitor { select_index }; + + while (entry_visitor.index < population_menu.province_list_entries.size()) { + std::visit(entry_visitor, population_menu.province_list_entries[entry_visitor.index]); + entry_visitor.index++; + } + + emit_signal( + _signal_population_menu_province_list_selected_changed(), + set_scroll_index ? entry_visitor.selected_visible_index : -1 + ); + + _population_menu_update_pops(); + + return OK; +} + +Error MenuSingleton::population_menu_select_province(int32_t province_index) { + ERR_FAIL_NULL_V(game_manager, FAILED); + + ERR_FAIL_COND_V(province_index <= 0 || province_index > game_manager->get_map().get_province_count(), FAILED); + + struct entry_visitor_t { + + MenuSingleton& menu_singleton; + + const int32_t _province_index; + + int32_t index = 0; + + int32_t state_entry_to_expand = -1; + + bool ret = true; + + /* Overloads return false if the province entry is found and the loop can stop, true otherwise. */ + + bool operator()(population_menu_t::country_entry_t& country_entry) { + return true; + } + + bool operator()(population_menu_t::state_entry_t& state_entry) { + if (state_entry.expanded) { + state_entry_to_expand = -1; + } else { + state_entry_to_expand = index; + } + return true; + } + + bool operator()(population_menu_t::province_entry_t& province_entry) { + if (province_entry.province.get_index() == _province_index) { + + if (state_entry_to_expand >= 0) { + ret &= menu_singleton.population_menu_toggle_expanded(state_entry_to_expand, false) == OK; + } + + ret &= menu_singleton.population_menu_select_province_list_entry(index, true) == OK; + + return false; + } + return true; + } + + } entry_visitor { *this, province_index }; + + while (entry_visitor.index < population_menu.province_list_entries.size() + && std::visit(entry_visitor, population_menu.province_list_entries[entry_visitor.index])) { + entry_visitor.index++; + } + + ERR_FAIL_COND_V_MSG( + entry_visitor.index >= population_menu.province_list_entries.size(), FAILED, + vformat("Cannot select province index %d - not found in population menu province list!", province_index) + ); + + return ERR(entry_visitor.ret); +} + +Error MenuSingleton::population_menu_toggle_expanded(int32_t toggle_index, bool emit_selected_changed) { + ERR_FAIL_INDEX_V(toggle_index, population_menu.province_list_entries.size(), FAILED); + + population_menu_t::state_entry_t* state_entry = + std::get_if(&population_menu.province_list_entries[toggle_index]); + + ERR_FAIL_NULL_V_MSG(state_entry, FAILED, vformat("Cannot toggle expansion of a non-state entry! (%d)", toggle_index)); + + int32_t provinces = 0; + + while (++toggle_index < population_menu.province_list_entries.size() + && std::holds_alternative(population_menu.province_list_entries[toggle_index])) { + provinces++; + } + + if (state_entry->expanded) { + state_entry->expanded = false; + population_menu.visible_province_list_entries -= provinces; + } else { + state_entry->expanded = true; + population_menu.visible_province_list_entries += provinces; + } + + emit_signal(_signal_population_menu_province_list_changed()); + + if (emit_selected_changed) { + emit_signal(_signal_population_menu_province_list_selected_changed(), -1); + } + + return OK; +} + +void MenuSingleton::_population_menu_update_pops() { + for (auto [pop_type, filter] : mutable_iterator(population_menu.pop_filters)) { + filter.count = 0; + filter.promotion_demotion_change = 0; + } + + population_menu.pops.clear(); + + for (int32_t index = 0; index < population_menu.province_list_entries.size(); index++) { + population_menu_t::province_entry_t const* province_entry = + std::get_if(&population_menu.province_list_entries[index]); + + if (province_entry != nullptr && province_entry->selected) { + for (Pop const& pop : province_entry->province.get_pops()) { + population_menu.pops.push_back(&pop); + population_menu_t::pop_filter_t& filter = population_menu.pop_filters[&pop.get_type()]; + filter.count += pop.get_size(); + // TODO - set filter.promotion_demotion_change + } + } + } + + _population_menu_update_filtered_pops(); +} + +void MenuSingleton::_population_menu_update_filtered_pops() { + population_menu.filtered_pops.clear(); + + for (fixed_point_map_t& distribution : population_menu.distributions) { + distribution.clear(); + } + + for (Pop const* pop : population_menu.pops) { + if (population_menu.pop_filters[&pop->get_type()].selected) { + population_menu.filtered_pops.push_back(pop); + } + } + + for (Pop const* pop : population_menu.filtered_pops) { + population_menu.distributions[0][&pop->get_type()] += pop->get_size(); + population_menu.distributions[1][&pop->get_religion()] += pop->get_size(); + population_menu.distributions[2] += + cast_map(pop->get_ideologies() * static_cast(pop->get_size())); + population_menu.distributions[3][&pop->get_culture()] += pop->get_size(); + population_menu.distributions[4] += + cast_map(pop->get_issues() * static_cast(pop->get_size())); + population_menu.distributions[5] += + cast_map(pop->get_votes() * static_cast(pop->get_size())); + } + + for (fixed_point_map_t& distribution : population_menu.distributions) { + normalise_fixed_point_map(distribution); + } + + _population_menu_sort_pops(); +} + +MenuSingleton::sort_func_t MenuSingleton::_get_population_menu_sort_func(population_menu_t::PopSortKey sort_key) const { + using enum population_menu_t::PopSortKey; + switch (sort_key) { + case SORT_SIZE: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_size() < b->get_size(); + }; + case SORT_TYPE: + return [this](Pop const* a, Pop const* b) -> bool { + return tr(std_view_to_godot_string(a->get_type().get_identifier())) + < tr(std_view_to_godot_string(b->get_type().get_identifier())); + }; + case SORT_CULTURE: + return [this](Pop const* a, Pop const* b) -> bool { + return tr(std_view_to_godot_string(a->get_culture().get_identifier())) + < tr(std_view_to_godot_string(b->get_culture().get_identifier())); + }; + case SORT_RELIGION: + return [this](Pop const* a, Pop const* b) -> bool { + return tr(std_view_to_godot_string(a->get_religion().get_identifier())) + < tr(std_view_to_godot_string(b->get_religion().get_identifier())); + }; + case SORT_LOCATION: + return [this](Pop const* a, Pop const* b) -> bool { + return tr(a->get_location() != nullptr ? std_view_to_godot_string(a->get_location()->get_identifier()) : String {}) + < tr(b->get_location() != nullptr ? std_view_to_godot_string(b->get_location()->get_identifier()) : String {}); + }; + case SORT_MILITANCY: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_militancy() < b->get_militancy(); + }; + case SORT_CONSCIOUSNESS: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_consciousness() < b->get_consciousness(); + }; + case SORT_IDEOLOGY: + return [](Pop const* a, Pop const* b) -> bool { + return sorted_fixed_map_less_than(a->get_ideologies(), b->get_ideologies()); + }; + case SORT_ISSUES: + return [](Pop const* a, Pop const* b) -> bool { + return sorted_fixed_map_less_than(a->get_issues(), b->get_issues()); + }; + case SORT_UNEMPLOYMENT: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_unemployment() < b->get_unemployment(); + }; + case SORT_CASH: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_cash() < b->get_cash(); + }; + case SORT_LIFE_NEEDS: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_life_needs_fulfilled() < b->get_life_needs_fulfilled(); + }; + case SORT_EVERYDAY_NEEDS: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_everyday_needs_fulfilled() < b->get_everyday_needs_fulfilled(); + }; + case SORT_LUXURY_NEEDS: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_luxury_needs_fulfilled() < b->get_luxury_needs_fulfilled(); + }; + case SORT_REBEL_FACTION: + return [](Pop const* a, Pop const* b) -> bool { return false; }; // TODO - implement + case SORT_SIZE_CHANGE: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_total_change() < b->get_total_change(); + }; + case SORT_LITERACY: + return [](Pop const* a, Pop const* b) -> bool { + return a->get_literacy() < b->get_literacy(); + }; + default: + UtilityFunctions::push_error("Invalid population menu sort key: ", sort_key); + return [](Pop const* a, Pop const* b) -> bool { return false; }; + } +} + +void MenuSingleton::_population_menu_sort_pops() { + if (population_menu.sort_key != population_menu_t::NONE) { + const sort_func_t base_sort_func = _get_population_menu_sort_func(population_menu.sort_key); + + const sort_func_t sort_func = population_menu.sort_descending + ? base_sort_func + : [base_sort_func](Pop const* a, Pop const* b) { return base_sort_func(b, a); }; + + std::sort(population_menu.filtered_pops.begin(), population_menu.filtered_pops.end(), sort_func); + } + + emit_signal(_signal_population_menu_pops_changed()); +} + +Error MenuSingleton::population_menu_select_sort_key(population_menu_t::PopSortKey sort_key) { + using enum population_menu_t::PopSortKey; + /* sort_key must be cast here to avoid causing clang to segfault during compilation. */ + ERR_FAIL_INDEX_V_MSG( + static_cast(sort_key), static_cast(MAX_SORT_KEY), FAILED, + vformat("Invalid population menu sort key: %d (must be under %d)", sort_key, MAX_SORT_KEY) + ); + + if (sort_key == population_menu.sort_key) { + /* Re-selecting the current sort key reverses sort order. */ + population_menu.sort_descending = !population_menu.sort_descending; + } else { + /* Selecting a new sort key switches sorting to that key, preserving the existing sort order. */ + population_menu.sort_key = sort_key; + } + + _population_menu_sort_pops(); + + return OK; +} + +TypedArray MenuSingleton::get_population_menu_pop_rows(int32_t start, int32_t count) const { + if (population_menu.filtered_pops.empty()) { + return {}; + } + ERR_FAIL_INDEX_V_MSG( + start, population_menu.filtered_pops.size(), {}, vformat("Invalid start for population menu pop rows: %d", start) + ); + ERR_FAIL_COND_V_MSG(count <= 0, {}, vformat("Invalid count for population menu pop rows: %d", count)); + + if (start + count > population_menu.filtered_pops.size()) { + count = population_menu.filtered_pops.size() - start; + } + + static const StringName pop_size_key = "size"; + + static const StringName pop_type_icon_key = "pop_type_icon"; + // TODO - pop type name + // TODO - promotions (target pop type and count) + // TODO - demotions (target pop type and count) + // TODO - good being produced (artisans, farmers, labourers, slaves) + // TODO - military unit and army (soldiers) + + static const StringName pop_culture_key = "culture"; + // TODO - cultural assimilation (primary/accepted, or number, target culture, and conditional weights breakdown) + + static const StringName pop_religion_icon_key = "religion_icon"; + // TODO - religion name + // TODO - religious conversion (accepted, or number, target religion, and conditional weights breakdown) + + static const StringName pop_location_key = "location"; + // TODO - internal, external and colonial migration + + static const StringName pop_militancy_key = "militancy"; + // TODO - monthly militancy change and modifier breakdown + + static const StringName pop_consciousness_key = "consciousness"; + // TODO - monthly consciousness change and modifier breakdown + + static const StringName pop_ideology_key = "ideology"; + + static const StringName pop_issues_key = "issues"; + + static const StringName pop_unemployment_key = "unemployment"; + + static const StringName pop_cash_key = "cash"; + // TODO - daily income, needs, salary and savings + + static const StringName pop_life_needs_key = "life_needs"; + static const StringName pop_everyday_needs_key = "everyday_needs"; + static const StringName pop_luxury_needs_key = "luxury_needs"; + // TODO - goods not available on market or goods not affordale + price (for all 3 needs types) + + // TODO - rebel faction icon and name/description + + static const StringName pop_size_change_key = "size_change"; + // TODO - size change breakdown + + static const StringName pop_literacy_key = "literacy"; + // TODO - monthly change + + TypedArray array; + + for (int32_t idx = start; idx < start + count; ++idx) { + Pop const* pop = population_menu.filtered_pops[idx]; + Dictionary pop_dict; + + pop_dict[pop_size_key] = pop->get_size(); + pop_dict[pop_type_icon_key] = pop->get_type().get_sprite(); + pop_dict[pop_culture_key] = std_view_to_godot_string(pop->get_culture().get_identifier()); + pop_dict[pop_religion_icon_key] = pop->get_religion().get_icon(); + pop_dict[pop_location_key] = + pop->get_location() != nullptr ? std_view_to_godot_string(pop->get_location()->get_identifier()) : String {}; + pop_dict[pop_militancy_key] = pop->get_militancy().to_float(); + pop_dict[pop_consciousness_key] = pop->get_consciousness().to_float(); + pop_dict[pop_ideology_key] = GFXPieChartTexture::distribution_to_slices_array(pop->get_ideologies()); + pop_dict[pop_issues_key] = GFXPieChartTexture::distribution_to_slices_array(pop->get_issues()); + pop_dict[pop_unemployment_key] = pop->get_unemployment().to_float(); + pop_dict[pop_cash_key] = pop->get_cash().to_float(); + pop_dict[pop_life_needs_key] = pop->get_life_needs_fulfilled().to_float(); + pop_dict[pop_everyday_needs_key] = pop->get_everyday_needs_fulfilled().to_float(); + pop_dict[pop_luxury_needs_key] = pop->get_luxury_needs_fulfilled().to_float(); + pop_dict[pop_size_change_key] = pop->get_total_change(); + pop_dict[pop_literacy_key] = pop->get_literacy().to_float(); + + array.push_back(pop_dict); + } + + return array; +} + +int32_t MenuSingleton::get_population_menu_pop_row_count() const { + return population_menu.filtered_pops.size(); +} + +PackedInt32Array MenuSingleton::get_population_menu_pop_filter_setup_info() { + ERR_FAIL_NULL_V(game_manager, {}); + + if (population_menu.pop_filters.empty()) { + for (PopType const& pop_type : game_manager->get_pop_manager().get_pop_types()) { + population_menu.pop_filters.emplace(&pop_type, population_menu_t::pop_filter_t { 0, 0, true }); + } + } + ERR_FAIL_COND_V_MSG(population_menu.pop_filters.empty(), {}, "Failed to generate population menu pop filters!"); + + PackedInt32Array array; + + for (auto const& [pop_type, filter] : population_menu.pop_filters) { + array.push_back(pop_type->get_sprite()); + } + + return array; +} + +TypedArray MenuSingleton::get_population_menu_pop_filter_info() const { + static const StringName pop_filter_count_key = "count"; + static const StringName pop_filter_change_key = "change"; + static const StringName pop_filter_selected_key = "selected"; + + TypedArray array; + + for (auto const& [pop_type, filter] : population_menu.pop_filters) { + Dictionary filter_dict; + filter_dict[pop_filter_count_key] = filter.count; + filter_dict[pop_filter_change_key] = filter.promotion_demotion_change; + filter_dict[pop_filter_selected_key] = filter.selected; + array.push_back(filter_dict); + } + + return array; +} + +Error MenuSingleton::population_menu_toggle_pop_filter(int32_t index) { + ERR_FAIL_COND_V_MSG( + index < 0 || index >= population_menu.pop_filters.size(), FAILED, vformat("Invalid pop filter index: %d", index) + ); + + population_menu_t::pop_filter_t& filter = mutable_iterator(population_menu.pop_filters).begin()[index].second; + filter.selected = !filter.selected; + + _population_menu_update_filtered_pops(); + + return OK; +} + +void MenuSingleton::population_menu_select_all_pop_filters() { + bool changed = false; + + for (auto [pop_type, filter] : mutable_iterator(population_menu.pop_filters)) { + if (!filter.selected) { + filter.selected = true; + changed = true; + } + } + + if (changed) { + _population_menu_update_filtered_pops(); + } +} + +void MenuSingleton::population_menu_deselect_all_pop_filters() { + bool changed = false; + for (auto [pop_type, filter] : mutable_iterator(population_menu.pop_filters)) { + if (filter.selected) { + filter.selected = false; + changed = true; + } + } + if (changed) { + _population_menu_update_filtered_pops(); + } +} + +PackedStringArray MenuSingleton::get_population_menu_distribution_setup_info() const { + static const PackedStringArray distribution_names = []() -> PackedStringArray { + PackedStringArray array; + + for (char const* name : std::array { + /* Workforce (PopType) */ "WORKFORCE_DISTTITLE", + /* Religion */ "RELIGION_DISTTITLE", + /* Ideology */ "IDEOLOGY_DISTTITLE", + /* Nationality (Culture) */ "NATIONALITY_DISTTITLE", + /* Issues */ "DOMINANT_ISSUES_DISTTITLE", + /* Vote */ "ELECTORATE_DISTTITLE" + }) { + array.push_back(name); + } + + return array; + }(); + + return distribution_names; +} + +TypedArray MenuSingleton::get_population_menu_distribution_info() const { + TypedArray array; + + for (fixed_point_map_t const& distribution : population_menu.distributions) { + array.push_back(GFXPieChartTexture::distribution_to_slices_array(distribution)); + } + + return array; +} -- cgit v1.2.3-56-ga3b1