aboutsummaryrefslogtreecommitdiff
path: root/extension
diff options
context:
space:
mode:
Diffstat (limited to 'extension')
m---------extension/deps/openvic-simulation0
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.cpp7
-rw-r--r--extension/src/openvic-extension/singletons/MenuSingleton.cpp69
-rw-r--r--extension/src/openvic-extension/singletons/MenuSingleton.hpp96
-rw-r--r--extension/src/openvic-extension/singletons/PopulationMenu.cpp712
5 files changed, 882 insertions, 2 deletions
diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation
-Subproject c11d262a4d2c987c8cf8e0d4b24929cbe56bb28
+Subproject 020ada6c8f0f1bf4486fd9e76ec29673044794d
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 <godot_cpp/variant/utility_functions.hpp>
-
#include <openvic-simulation/GameManager.hpp>
#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 <godot_cpp/classes/image.hpp>
+#include <openvic-simulation/pop/Pop.hpp>
#include <openvic-simulation/types/OrderedContainers.hpp>
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<country_entry_t, state_entry_t, province_entry_t>;
+
+ std::vector<province_list_entry_t> 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<PopType const*, pop_filter_t> pop_filters;
+
+ static constexpr int32_t DISTRIBUTION_COUNT = 6;
+ /* Distributions:
+ * - Workforce (PopType)
+ * - Religion
+ * - Ideology
+ * - Nationality (Culture)
+ * - Issues
+ * - Vote */
+ std::array<fixed_point_map_t<HasIdentifierAndColour const*>, 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<Pop const*> 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<godot::Dictionary> 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<bool(Pop const*, Pop const*)>;
+ 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<godot::Dictionary> 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<godot::Dictionary> 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<godot::Array> 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 <godot_cpp/variant/utility_functions.hpp>
+
+#include <openvic-simulation/GameManager.hpp>
+
+#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<Dictionary> 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<Dictionary> 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_t::state_entry_t>(&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_t::province_entry_t>(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_t::province_entry_t>(&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<HasIdentifierAndColour const*>& 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<HasIdentifierAndColour>(pop->get_ideologies() * static_cast<int32_t>(pop->get_size()));
+ population_menu.distributions[3][&pop->get_culture()] += pop->get_size();
+ population_menu.distributions[4] +=
+ cast_map<HasIdentifierAndColour>(pop->get_issues() * static_cast<int32_t>(pop->get_size()));
+ population_menu.distributions[5] +=
+ cast_map<HasIdentifierAndColour>(pop->get_votes() * static_cast<int32_t>(pop->get_size()));
+ }
+
+ for (fixed_point_map_t<HasIdentifierAndColour const*>& 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<int32_t>(sort_key), static_cast<int32_t>(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<Dictionary> 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<Dictionary> 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<Dictionary> 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<Dictionary> 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<char const*, population_menu_t::DISTRIBUTION_COUNT> {
+ /* 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<Array> MenuSingleton::get_population_menu_distribution_info() const {
+ TypedArray<Array> array;
+
+ for (fixed_point_map_t<HasIdentifierAndColour const*> const& distribution : population_menu.distributions) {
+ array.push_back(GFXPieChartTexture::distribution_to_slices_array(distribution));
+ }
+
+ return array;
+}