diff --git a/CMakeLists.txt b/CMakeLists.txt index b1a346a0a..b2150ba77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ list(APPEND ALICE_CORE_SOURCES_LIST "${PROJECT_SOURCE_DIR}/src/map/map_data_loading.cpp" "${PROJECT_SOURCE_DIR}/src/map/map_borders.cpp" "${PROJECT_SOURCE_DIR}/src/map/map.cpp" + "${PROJECT_SOURCE_DIR}/src/map/flow_map.cpp" "${PROJECT_SOURCE_DIR}/src/graphics/xac.cpp" "${PROJECT_SOURCE_DIR}/src/graphics/stateless_render.cpp" "${PROJECT_SOURCE_DIR}/src/lunasvg/graphics.cpp" diff --git a/assets/alice.gui b/assets/alice.gui index a7429d1e3..861ac3103 100644 --- a/assets/alice.gui +++ b/assets/alice.gui @@ -157,18 +157,25 @@ guiTypes = { iconType = { - name = "gfx_storage_unit_types" - spriteType = "GFX_unit_strip" - position = { -150 -150 } - Orientation = "UPPER_LEFT" - } + name = "gfx_storage_unit_types" + spriteType = "GFX_unit_strip" + position = { -150 -150 } + Orientation = "UPPER_LEFT" + } - iconType = { - name = "gfx_storage_commodity" - spriteType = "GFX_resources_big" - position = { -150 -150 } - Orientation = "UPPER_LEFT" - } + iconType = { + name = "gfx_storage_commodity" + spriteType = "GFX_resources_big" + position = { -150 -150 } + Orientation = "UPPER_LEFT" + } + + iconType = { + name = "gfx_storage_pop_types" + spriteType = "GFX_pops_small" + position = { -150 -150 } + Orientation = "UPPER_LEFT" + } instantTextBoxType = { name = "alice_investment_text" diff --git a/assets/localisation/en-US/settings.csv b/assets/localisation/en-US/settings.csv index 0237a8d75..d4a216872 100644 --- a/assets/localisation/en-US/settings.csv +++ b/assets/localisation/en-US/settings.csv @@ -62,3 +62,6 @@ graphics_details_1;Classic graphics_details_2;Modern graphics_details_3;Gores disband_confirmation_label;Unit disband confirmation +trade_particles_section_label;Trade particles settings +reset_trade_particles;Reset trade particles +trade_particles_count;Particles count diff --git a/assets/main_menu.aui b/assets/main_menu.aui index 3c0d92600..7b9ccec19 100644 Binary files a/assets/main_menu.aui and b/assets/main_menu.aui differ diff --git a/src/gamestate/system_state.cpp b/src/gamestate/system_state.cpp index 1e6adcc9b..0a94ea465 100644 --- a/src/gamestate/system_state.cpp +++ b/src/gamestate/system_state.cpp @@ -1616,6 +1616,7 @@ void state::save_user_settings() const { US_SAVE(locale); US_SAVE(graphics_mode); US_SAVE(unit_disband_confirmation); + US_SAVE(trade_particles_count); #undef US_SAVE simple_fs::write_file(settings_location, NATIVE("user_settings.dat"), &buffer[0], uint32_t(ptr - buffer)); @@ -1686,6 +1687,7 @@ void state::load_user_settings() { US_LOAD(locale); US_LOAD(graphics_mode); US_LOAD(unit_disband_confirmation); + US_LOAD(trade_particles_count); #undef US_LOAD } while(false); @@ -3811,10 +3813,15 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day // return; //} bool sea_route = false; - if(p2.index() >= province_definitions.first_sea_province.index() || p1.index() >= province_definitions.first_sea_province.index()) { + if( + p2.index() >= province_definitions.first_sea_province.index() + || p1.index() >= province_definitions.first_sea_province.index() + ) { sea_route = true; } + auto ignore_sea_land_province = world.province_adjacency_get_canal_or_blockade_province(adj); + auto mid_point_1 = world.province_get_mid_point(p1); auto mid_point_2 = world.province_get_mid_point(p2); @@ -3856,6 +3863,7 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day auto handle_next = [&](int x, int y, int next_x, int next_y, float base_distance, int8_t direction, int8_t dx, int8_t dy, uint8_t speed, float prev_dist) { auto next_idx = next_x + next_y * map_state.map_data.size_x; + auto local_idx = x + y * map_state.map_data.size_x; if(!visited[next_idx]) { for(int i = 1; i <= (int)speed; i++) { auto nx = x + dx * i; @@ -3869,9 +3877,18 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day auto modifier = context.modifier_by_terrain_index[local_terrain]; auto& modifier_data = world.modifier_get_province_values(modifier); auto move_cost_mod = terrain_cost[local_terrain]; + dcon::province_id local_province = province::from_map_id(map_state.map_data.province_id_map[local_idx]); + dcon::province_id next_province = province::from_map_id(map_state.map_data.province_id_map[next_idx]); + bool apply_malus = true; + if(ignore_sea_land_province) { + apply_malus = (ignore_sea_land_province != local_province) && (ignore_sea_land_province != next_province); + } if(sea_route) { - move_cost_mod = 100.f; - if (local_terrain == 255) move_cost_mod = 1.f; + if(local_terrain != 255 && apply_malus) { + move_cost_mod = 100.f; + } else { + move_cost_mod = 1.f; + } } auto next_distance = prev_dist + base_distance * std::max(0.05f, (move_cost_mod * 20.f)); auto scaled_x = (float)next_x / (float)map_state.map_data.size_x; @@ -3975,6 +3992,7 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day int step = 0; glm::vec2 current_pos{ end_x, end_y }; + glm::vec2 temp_prev = current_pos; while(end_idx != start_idx && (int)(end_idx) >= 0) { @@ -4019,21 +4037,28 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, +start_normal, 0.0f, distance }); map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, -start_normal, 1.0f, distance }); + assert(norm_pos != norm_next); + end_idx = next_idx; + temp_prev = current_pos; current_pos = next_pos; } glm::vec2 next_pos = put_in_local(mid_point_1, current_pos, float(map_state.map_data.size_x)); distance += glm::length(next_pos - current_pos) / float(map_state.map_data.size_y); - glm::vec2 prev_perpendicular = glm::normalize(mid_point_1 - current_pos); - auto start_normal = glm::vec2(-prev_perpendicular.y, prev_perpendicular.x); - auto norm_pos = current_pos / glm::vec2(map_state.map_data.size_x, map_state.map_data.size_y); - auto norm_next = next_pos / glm::vec2(map_state.map_data.size_x, map_state.map_data.size_y); - map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_pos, +start_normal, 0.0f, 0.f });//C - map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_pos, -start_normal, 1.0f, 0.f });//D - map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, +start_normal, 0.0f, distance }); - map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, -start_normal, 1.0f, distance }); + if(mid_point_1 != current_pos) { + glm::vec2 prev_perpendicular = glm::normalize(mid_point_1 - current_pos); + auto start_normal = glm::vec2(-prev_perpendicular.y, prev_perpendicular.x); + auto norm_pos = current_pos / glm::vec2(map_state.map_data.size_x, map_state.map_data.size_y); + auto norm_next = next_pos / glm::vec2(map_state.map_data.size_x, map_state.map_data.size_y); + + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_pos, +start_normal, 0.0f, 0.f });//C + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_pos, -start_normal, 1.0f, 0.f });//D + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, +start_normal, 0.0f, distance }); + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, -start_normal, 1.0f, distance }); + } + map_state.map_data.railroad_counts.push_back(GLsizei(map_state.map_data.railroad_vertices.size() - map_state.map_data.railroad_starts.back())); clear_visited(); diff --git a/src/gamestate/system_state.hpp b/src/gamestate/system_state.hpp index 0c6f00b7b..e7eb89c7f 100644 --- a/src/gamestate/system_state.hpp +++ b/src/gamestate/system_state.hpp @@ -30,6 +30,7 @@ #include "immediate_mode_state.hpp" #include "network_containers.hpp" #include "container_types_ui.hpp" +#include "flow_map.hpp" namespace game_scene { scene_properties nation_picker(); @@ -401,6 +402,7 @@ struct user_settings_s { sys::graphics_mode graphics_mode = sys::graphics_mode::classic; uint32_t UNUSED_UINT32_T = 0; char locale[16] = "en-US"; + uint8_t trade_particles_count = 2; }; @@ -930,7 +932,8 @@ struct alignas(64) state { std::atomic ui_pause = false; // force pause by an important message being open std::atomic railroad_built = true; // game state -> map std::atomic sprawl_update_requested = true; - std::atomic update_trade_flow = true; + + flow_map::flow_map_data flow_map { }; std::atomic tick_start_counter; std::atomic tick_end_counter; diff --git a/src/gui/economy_viewer.cpp b/src/gui/economy_viewer.cpp index 20fb3ad9c..8e1674b55 100644 --- a/src/gui/economy_viewer.cpp +++ b/src/gui/economy_viewer.cpp @@ -1192,7 +1192,7 @@ void render(sys::state& state) { state.selected_trade_good = cid; } update(state); - state.update_trade_flow.store(true, std::memory_order::release); + state.flow_map.request_update(flow_map::data_source::commodity); } state.iui_state.localized_string_r( state, (int32_t)static_elements::commodities + cid.index(), diff --git a/src/gui/main_menu.cpp b/src/gui/main_menu.cpp index 23d793b33..55decdcca 100644 --- a/src/gui/main_menu.cpp +++ b/src/gui/main_menu.cpp @@ -1,8 +1,7 @@ +// BEGIN prelude +// END + namespace alice_ui { -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wswitch" -#endif struct main_menu_base_save_and_exit_t; struct main_menu_base_save_t; struct main_menu_base_exit_t; @@ -61,6 +60,8 @@ struct main_menu_graphics_black_map_font_label_t; struct main_menu_graphics_river_label_t; struct main_menu_graphics_railroad_label_t; struct main_menu_graphics_fonts_label_t; +struct main_menu_graphics_trade_particles_count_dropdown_t; +struct main_menu_graphics_trade_particles_label_t; struct main_menu_graphics_t; struct main_menu_message_settings_message_notify_rebels_defeat_t; struct main_menu_message_settings_reset_t; @@ -499,6 +500,27 @@ struct main_menu_graphics_fonts_label_t : public alice_ui::template_toggle_butto bool button_action(sys::state& state) noexcept override; void on_update(sys::state& state) noexcept override; }; +struct main_menu_graphics_trade_particles_count_dropdown_t : public alice_ui::template_drop_down_control { +// BEGIN graphics::trade_particles_count_dropdown::variables +// END + struct string_dropdown_choice_option { int32_t index; std::string display_value; }; + std::vector list_contents; + std::vector> list_pool; + std::unique_ptr label_window_internal; + void add_item( int32_t index, std::string display_value); + ui::element_base* get_nth_item(sys::state& state, int32_t id, int32_t pool_id) override; + void quiet_on_selection(sys::state& state, int32_t id); + void on_selection(sys::state& state, int32_t id) override; + void clear_list(); + void on_create(sys::state& state) noexcept override; + void on_update(sys::state& state) noexcept override; +}; +struct main_menu_graphics_trade_particles_label_t : public alice_ui::template_text_button { +// BEGIN graphics::trade_particles_label::variables +// END + bool button_action(sys::state& state) noexcept override; + void on_update(sys::state& state) noexcept override; +}; struct main_menu_message_settings_message_notify_rebels_defeat_t : public alice_ui::template_toggle_button { // BEGIN message_settings::message_notify_rebels_defeat::variables // END @@ -803,6 +825,10 @@ struct main_menu_graphics_t : public layout_window_element { std::unique_ptr river_label; std::unique_ptr railroad_label; std::unique_ptr fonts_label; + std::unique_ptr trade_particles_count_dropdown; + std::unique_ptr trade_particles_label; + std::unique_ptr trade_particles_section_label; + std::unique_ptr trade_particles_count_label; std::vector> gui_inserts; void create_layout_level(sys::state& state, layout_level& lvl, char const* ldata, size_t sz); void on_create(sys::state& state) noexcept override; @@ -1084,16 +1110,16 @@ void main_menu_base_save_name_t::edit_box_update(sys::state& state, std::u16stri base.save_warning_icon->impl_on_update(state); // END } -void main_menu_base_save_name_t::on_create(sys::state& state) noexcept { -// BEGIN base::save_name::create -// END -} void main_menu_base_save_name_t::on_update(sys::state& state) noexcept { main_menu_base_t& base = *((main_menu_base_t*)(parent)); // BEGIN base::save_name::update disabled = !state.current_scene.game_in_progress; // END } +void main_menu_base_save_name_t::on_create(sys::state& state) noexcept { +// BEGIN base::save_name::create +// END +} void main_menu_base_save_warning_icon_t::on_update(sys::state& state) noexcept { main_menu_base_t& base = *((main_menu_base_t*)(parent)); // BEGIN base::save_warning_icon::update @@ -1157,6 +1183,10 @@ void main_menu_base_t::create_layout_level(sys::state& state, layout_level& lvl, layout_item_types t; buffer.read(t); switch(t) { + case layout_item_types::control: + case layout_item_types::window: + case layout_item_types::generator: + abort(); case layout_item_types::texture_layer: { texture_layer temp; @@ -1164,13 +1194,15 @@ void main_menu_base_t::create_layout_level(sys::state& state, layout_level& lvl, buffer.read(temp.texture); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::control: + case layout_item_types::control2: { layout_control temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); temp.ptr = nullptr; if(cname == "bg_extension") { temp.ptr = bg_extension.get(); @@ -1220,13 +1252,15 @@ void main_menu_base_t::create_layout_level(sys::state& state, layout_level& lvl, } lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::window: + case layout_item_types::window2: { layout_window temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); if(cname == "base") { temp.ptr = make_main_menu_base(state); } @@ -1260,7 +1294,7 @@ void main_menu_base_t::create_layout_level(sys::state& state, layout_level& lvl, buffer.read(temp.amount); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::generator: + case layout_item_types::generator2: { generator_instance temp; std::string_view cname = buffer.read(); @@ -2097,6 +2131,10 @@ void main_menu_sound_t::create_layout_level(sys::state& state, layout_level& lvl layout_item_types t; buffer.read(t); switch(t) { + case layout_item_types::control: + case layout_item_types::window: + case layout_item_types::generator: + abort(); case layout_item_types::texture_layer: { texture_layer temp; @@ -2104,13 +2142,15 @@ void main_menu_sound_t::create_layout_level(sys::state& state, layout_level& lvl buffer.read(temp.texture); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::control: + case layout_item_types::control2: { layout_control temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); temp.ptr = nullptr; if(cname == "window_title") { temp.ptr = window_title.get(); @@ -2202,13 +2242,15 @@ void main_menu_sound_t::create_layout_level(sys::state& state, layout_level& lvl } lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::window: + case layout_item_types::window2: { layout_window temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); if(cname == "base") { temp.ptr = make_main_menu_base(state); } @@ -2242,7 +2284,7 @@ void main_menu_sound_t::create_layout_level(sys::state& state, layout_level& lvl buffer.read(temp.amount); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::generator: + case layout_item_types::generator2: { generator_instance temp; std::string_view cname = buffer.read(); @@ -3144,6 +3186,10 @@ void main_menu_misc_t::create_layout_level(sys::state& state, layout_level& lvl, layout_item_types t; buffer.read(t); switch(t) { + case layout_item_types::control: + case layout_item_types::window: + case layout_item_types::generator: + abort(); case layout_item_types::texture_layer: { texture_layer temp; @@ -3151,13 +3197,15 @@ void main_menu_misc_t::create_layout_level(sys::state& state, layout_level& lvl, buffer.read(temp.texture); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::control: + case layout_item_types::control2: { layout_control temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); temp.ptr = nullptr; if(cname == "window_title") { temp.ptr = window_title.get(); @@ -3213,13 +3261,15 @@ void main_menu_misc_t::create_layout_level(sys::state& state, layout_level& lvl, } lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::window: + case layout_item_types::window2: { layout_window temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); if(cname == "base") { temp.ptr = make_main_menu_base(state); } @@ -3253,7 +3303,7 @@ void main_menu_misc_t::create_layout_level(sys::state& state, layout_level& lvl, buffer.read(temp.amount); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::generator: + case layout_item_types::generator2: { generator_instance temp; std::string_view cname = buffer.read(); @@ -3645,6 +3695,10 @@ void main_menu_string_dropdown_choice_t::create_layout_level(sys::state& state, layout_item_types t; buffer.read(t); switch(t) { + case layout_item_types::control: + case layout_item_types::window: + case layout_item_types::generator: + abort(); case layout_item_types::texture_layer: { texture_layer temp; @@ -3652,13 +3706,15 @@ void main_menu_string_dropdown_choice_t::create_layout_level(sys::state& state, buffer.read(temp.texture); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::control: + case layout_item_types::control2: { layout_control temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); temp.ptr = nullptr; if(cname == "contents") { temp.ptr = contents.get(); @@ -3672,13 +3728,15 @@ void main_menu_string_dropdown_choice_t::create_layout_level(sys::state& state, } lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::window: + case layout_item_types::window2: { layout_window temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); if(cname == "base") { temp.ptr = make_main_menu_base(state); } @@ -3712,7 +3770,7 @@ void main_menu_string_dropdown_choice_t::create_layout_level(sys::state& state, buffer.read(temp.amount); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::generator: + case layout_item_types::generator2: { generator_instance temp; std::string_view cname = buffer.read(); @@ -4223,6 +4281,66 @@ bool main_menu_graphics_fonts_label_t::button_action(sys::state& state) noexcept // END return true; } +void main_menu_graphics_trade_particles_count_dropdown_t::add_item( int32_t index, std::string display_value) { + list_contents.emplace_back(string_dropdown_choice_option{index, display_value}); + ++total_items; +} +ui::element_base* main_menu_graphics_trade_particles_count_dropdown_t::get_nth_item(sys::state& state, int32_t id, int32_t pool_id) { + while(pool_id >= int32_t(list_pool.size())) { + list_pool.emplace_back(static_cast(make_main_menu_string_dropdown_choice(state).release())); + } + list_pool[pool_id]->index = list_contents[id].index; + list_pool[pool_id]->display_value = list_contents[id].display_value; + return list_pool[pool_id].get(); +} +void main_menu_graphics_trade_particles_count_dropdown_t::quiet_on_selection(sys::state& state, int32_t id) { + if(id < 0 || id >= int32_t(list_contents.size())) return; + selected_item = id; + label_window_internal->index = list_contents[id].index; + label_window_internal->display_value = list_contents[id].display_value; + label_window_internal->impl_on_update(state); +} +void main_menu_graphics_trade_particles_count_dropdown_t::on_selection(sys::state& state, int32_t id) { + quiet_on_selection(state, id); + main_menu_graphics_t& graphics = *((main_menu_graphics_t*)(parent)); +// BEGIN graphics::trade_particles_count_dropdown::on_selection + state.user_settings.trade_particles_count = (uint8_t)id; + state.user_setting_changed = true; +// END +} +void main_menu_graphics_trade_particles_count_dropdown_t::clear_list() { list_contents.clear(); + total_items = 0; +} +void main_menu_graphics_trade_particles_count_dropdown_t::on_update(sys::state& state) noexcept { + main_menu_graphics_t& graphics = *((main_menu_graphics_t*)(parent)); +// BEGIN graphics::trade_particles_count_dropdown::update + clear_list(); + add_item(0, "0"); + add_item(1, "1000"); + add_item(2, "2000"); + add_item(3, "4000"); + add_item(4, "8000"); + add_item(5, "16000"); + quiet_on_selection(state, state.user_settings.trade_particles_count); +// END +} +void main_menu_graphics_trade_particles_count_dropdown_t::on_create(sys::state& state) noexcept { + template_drop_down_control::on_create(state); +// BEGIN graphics::trade_particles_count_dropdown::create +// END +} +void main_menu_graphics_trade_particles_label_t::on_update(sys::state& state) noexcept { + main_menu_graphics_t& graphics = *((main_menu_graphics_t*)(parent)); +// BEGIN graphics::trade_particles_label::update +// END +} +bool main_menu_graphics_trade_particles_label_t::button_action(sys::state& state) noexcept { + main_menu_graphics_t& graphics = *((main_menu_graphics_t*)(parent)); +// BEGIN graphics::trade_particles_label::lbutton_action + state.flow_map.request_update(flow_map::data_source::commodity); +// END + return true; +} ui::message_result main_menu_graphics_t::on_lbutton_down(sys::state& state, int32_t x, int32_t y, sys::key_modifiers mods) noexcept { state.ui_state.drag_target = this; return ui::message_result::consumed; @@ -4270,6 +4388,10 @@ void main_menu_graphics_t::create_layout_level(sys::state& state, layout_level& layout_item_types t; buffer.read(t); switch(t) { + case layout_item_types::control: + case layout_item_types::window: + case layout_item_types::generator: + abort(); case layout_item_types::texture_layer: { texture_layer temp; @@ -4277,13 +4399,15 @@ void main_menu_graphics_t::create_layout_level(sys::state& state, layout_level& buffer.read(temp.texture); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::control: + case layout_item_types::control2: { layout_control temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); temp.ptr = nullptr; if(cname == "title") { temp.ptr = title.get(); @@ -4348,6 +4472,18 @@ void main_menu_graphics_t::create_layout_level(sys::state& state, layout_level& if(cname == "fonts_label") { temp.ptr = fonts_label.get(); } else + if(cname == "trade_particles_count_dropdown") { + temp.ptr = trade_particles_count_dropdown.get(); + } else + if(cname == "trade_particles_label") { + temp.ptr = trade_particles_label.get(); + } else + if(cname == "trade_particles_section_label") { + temp.ptr = trade_particles_section_label.get(); + } else + if(cname == "trade_particles_count_label") { + temp.ptr = trade_particles_count_label.get(); + } else { std::string str_cname {cname}; auto found = scripted_elements.find(str_cname); @@ -4357,13 +4493,15 @@ void main_menu_graphics_t::create_layout_level(sys::state& state, layout_level& } lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::window: + case layout_item_types::window2: { layout_window temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); if(cname == "base") { temp.ptr = make_main_menu_base(state); } @@ -4397,7 +4535,7 @@ void main_menu_graphics_t::create_layout_level(sys::state& state, layout_level& buffer.read(temp.amount); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::generator: + case layout_item_types::generator2: { generator_instance temp; std::string_view cname = buffer.read(); @@ -4820,6 +4958,80 @@ void main_menu_graphics_t::on_create(sys::state& state) noexcept { children.push_back(cptr); pending_children.pop_back(); continue; } else + if(child_data.name == "trade_particles_count_dropdown") { + trade_particles_count_dropdown = std::make_unique(); + trade_particles_count_dropdown->parent = this; + auto cptr = trade_particles_count_dropdown.get(); + cptr->base_data.position.x = child_data.x_pos; + cptr->base_data.position.y = child_data.y_pos; + cptr->base_data.size.x = child_data.x_size; + cptr->base_data.size.y = child_data.y_size; + cptr->template_id = child_data.template_id; + cptr->two_columns = (child_data.text_type != aui_text_type::body); + cptr->target_page_height = child_data.border_size; + cptr->label_window_internal = std::unique_ptr(static_cast(make_main_menu_string_dropdown_choice(state).release())); + cptr->element_x_size = cptr->label_window_internal->base_data.size.x; + cptr->element_y_size = cptr->label_window_internal->base_data.size.y; + cptr->label_window = cptr->label_window_internal.get(); + cptr->parent = this; + cptr->on_create(state); + children.push_back(cptr); + pending_children.pop_back(); continue; + } else + if(child_data.name == "trade_particles_label") { + trade_particles_label = std::make_unique(); + trade_particles_label->parent = this; + auto cptr = trade_particles_label.get(); + cptr->base_data.position.x = child_data.x_pos; + cptr->base_data.position.y = child_data.y_pos; + cptr->base_data.size.x = child_data.x_size; + cptr->base_data.size.y = child_data.y_size; + cptr->template_id = child_data.template_id; + if(child_data.text_key.length() > 0) + cptr->default_text = state.lookup_key(child_data.text_key); + if(child_data.tooltip_text_key.length() > 0) + cptr->default_tooltip = state.lookup_key(child_data.tooltip_text_key); + cptr->parent = this; + cptr->on_create(state); + children.push_back(cptr); + pending_children.pop_back(); continue; + } else + if(child_data.name == "trade_particles_section_label") { + trade_particles_section_label = std::make_unique(); + trade_particles_section_label->parent = this; + auto cptr = trade_particles_section_label.get(); + cptr->base_data.position.x = child_data.x_pos; + cptr->base_data.position.y = child_data.y_pos; + cptr->base_data.size.x = child_data.x_size; + cptr->base_data.size.y = child_data.y_size; + cptr->template_id = child_data.template_id; + if(child_data.text_key.length() > 0) + cptr->default_text = state.lookup_key(child_data.text_key); + if(child_data.tooltip_text_key.length() > 0) + cptr->default_tooltip = state.lookup_key(child_data.tooltip_text_key); + cptr->parent = this; + cptr->on_create(state); + children.push_back(cptr); + pending_children.pop_back(); continue; + } else + if(child_data.name == "trade_particles_count_label") { + trade_particles_count_label = std::make_unique(); + trade_particles_count_label->parent = this; + auto cptr = trade_particles_count_label.get(); + cptr->base_data.position.x = child_data.x_pos; + cptr->base_data.position.y = child_data.y_pos; + cptr->base_data.size.x = child_data.x_size; + cptr->base_data.size.y = child_data.y_size; + cptr->template_id = child_data.template_id; + if(child_data.text_key.length() > 0) + cptr->default_text = state.lookup_key(child_data.text_key); + if(child_data.tooltip_text_key.length() > 0) + cptr->default_tooltip = state.lookup_key(child_data.tooltip_text_key); + cptr->parent = this; + cptr->on_create(state); + children.push_back(cptr); + pending_children.pop_back(); continue; + } else if (child_data.is_lua) { std::string str_name {child_data.name}; scripted_elements[str_name] = std::make_unique(); @@ -4890,7 +5102,7 @@ measure_result main_menu_message_settings_message_settings_list_t::place_item(s destination->children.push_back(message_setting_header_pool[message_setting_header_pool_used].get()); ((main_menu_message_setting_header_t*)(message_setting_header_pool[message_setting_header_pool_used].get()))->set_alternate(alternate); message_setting_row_pool[message_setting_row_pool_used]->base_data.position.x = int16_t(x); - message_setting_row_pool[message_setting_row_pool_used]->base_data.position.y = int16_t(y + message_setting_row_pool[0]->base_data.size.y + 0); + message_setting_row_pool[message_setting_row_pool_used]->base_data.position.y = int16_t(y + message_setting_header_pool[0]->base_data.size.y + 0); message_setting_row_pool[message_setting_row_pool_used]->parent = destination; destination->children.push_back(message_setting_row_pool[message_setting_row_pool_used].get()); ((main_menu_message_setting_row_t*)(message_setting_row_pool[message_setting_row_pool_used].get()))->id = std::get(values[index]).id; @@ -4899,7 +5111,8 @@ measure_result main_menu_message_settings_message_settings_list_t::place_item(s message_setting_header_pool_used++; message_setting_row_pool_used++; } - return measure_result{std::max(message_setting_header_pool[0]->base_data.size.x, message_setting_row_pool[0]->base_data.size.x), message_setting_header_pool[0]->base_data.size.y + message_setting_row_pool[0]->base_data.size.y + 0, measure_result::special::none}; + bool stick_to_next = false; + return measure_result{std::max(message_setting_header_pool[0]->base_data.size.x, message_setting_row_pool[0]->base_data.size.x), message_setting_header_pool[0]->base_data.size.y + message_setting_row_pool[0]->base_data.size.y + 0, stick_to_next ? measure_result::special::no_break : measure_result::special::none}; } if(destination) { if(message_setting_row_pool.size() <= size_t(message_setting_row_pool_used)) message_setting_row_pool.emplace_back(make_main_menu_message_setting_row(state)); @@ -4913,7 +5126,8 @@ measure_result main_menu_message_settings_message_settings_list_t::place_item(s message_setting_row_pool_used++; } alternate = !alternate; - return measure_result{ message_setting_row_pool[0]->base_data.size.x, message_setting_row_pool[0]->base_data.size.y + 0, measure_result::special::none}; + bool stick_to_next = false; + return measure_result{ message_setting_row_pool[0]->base_data.size.x, message_setting_row_pool[0]->base_data.size.y + 0, stick_to_next ? measure_result::special::no_break : measure_result::special::none}; } return measure_result{0,0,measure_result::special::none}; } @@ -5001,6 +5215,10 @@ void main_menu_message_settings_t::create_layout_level(sys::state& state, layout layout_item_types t; buffer.read(t); switch(t) { + case layout_item_types::control: + case layout_item_types::window: + case layout_item_types::generator: + abort(); case layout_item_types::texture_layer: { texture_layer temp; @@ -5008,13 +5226,15 @@ void main_menu_message_settings_t::create_layout_level(sys::state& state, layout buffer.read(temp.texture); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::control: + case layout_item_types::control2: { layout_control temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); temp.ptr = nullptr; if(cname == "title") { temp.ptr = title.get(); @@ -5034,13 +5254,15 @@ void main_menu_message_settings_t::create_layout_level(sys::state& state, layout } lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::window: + case layout_item_types::window2: { layout_window temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); if(cname == "base") { temp.ptr = make_main_menu_base(state); } @@ -5074,7 +5296,7 @@ void main_menu_message_settings_t::create_layout_level(sys::state& state, layout buffer.read(temp.amount); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::generator: + case layout_item_types::generator2: { generator_instance temp; std::string_view cname = buffer.read(); @@ -5629,6 +5851,10 @@ void main_menu_message_setting_row_t::create_layout_level(sys::state& state, lay layout_item_types t; buffer.read(t); switch(t) { + case layout_item_types::control: + case layout_item_types::window: + case layout_item_types::generator: + abort(); case layout_item_types::texture_layer: { texture_layer temp; @@ -5636,13 +5862,15 @@ void main_menu_message_setting_row_t::create_layout_level(sys::state& state, lay buffer.read(temp.texture); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::control: + case layout_item_types::control2: { layout_control temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); temp.ptr = nullptr; if(cname == "overlay") { temp.ptr = overlay.get(); @@ -5668,13 +5896,15 @@ void main_menu_message_setting_row_t::create_layout_level(sys::state& state, lay } lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::window: + case layout_item_types::window2: { layout_window temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); if(cname == "base") { temp.ptr = make_main_menu_base(state); } @@ -5708,7 +5938,7 @@ void main_menu_message_setting_row_t::create_layout_level(sys::state& state, lay buffer.read(temp.amount); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::generator: + case layout_item_types::generator2: { generator_instance temp; std::string_view cname = buffer.read(); @@ -6110,6 +6340,10 @@ void main_menu_message_setting_header_t::create_layout_level(sys::state& state, layout_item_types t; buffer.read(t); switch(t) { + case layout_item_types::control: + case layout_item_types::window: + case layout_item_types::generator: + abort(); case layout_item_types::texture_layer: { texture_layer temp; @@ -6117,13 +6351,15 @@ void main_menu_message_setting_header_t::create_layout_level(sys::state& state, buffer.read(temp.texture); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::control: + case layout_item_types::control2: { layout_control temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); temp.ptr = nullptr; if(cname == "contents") { temp.ptr = contents.get(); @@ -6137,13 +6373,15 @@ void main_menu_message_setting_header_t::create_layout_level(sys::state& state, } lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::window: + case layout_item_types::window2: { layout_window temp; std::string_view cname = buffer.read(); buffer.read(temp.abs_x); buffer.read(temp.abs_y); buffer.read(temp.absolute_position); + buffer.read(temp.fill_x); + buffer.read(temp.fill_y); if(cname == "base") { temp.ptr = make_main_menu_base(state); } @@ -6177,7 +6415,7 @@ void main_menu_message_setting_header_t::create_layout_level(sys::state& state, buffer.read(temp.amount); lvl.contents.emplace_back(std::move(temp)); } break; - case layout_item_types::generator: + case layout_item_types::generator2: { generator_instance temp; std::string_view cname = buffer.read(); @@ -6305,9 +6543,5 @@ std::unique_ptr make_main_menu_message_setting_header(sys::sta ptr->on_create(state); return ptr; } - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif // LOST-CODE } diff --git a/src/gui/market_trade_report.cpp b/src/gui/market_trade_report.cpp index 242bedc04..44e8aecc5 100644 --- a/src/gui/market_trade_report.cpp +++ b/src/gui/market_trade_report.cpp @@ -1069,7 +1069,7 @@ bool market_trade_report_commodity_entry_t::button_action(sys::state& state) noe } else { state.selected_trade_good = commodity.item; } - state.update_trade_flow.store(true, std::memory_order::release); + state.flow_map.request_update(flow_map::data_source::commodity); body.on_update(state); // END return true; @@ -1742,7 +1742,7 @@ bool market_trade_report_trade_item_select_market_t::button_action(sys::state& s auto pid_other = state.world.state_instance_get_capital(sid_other); state.set_selected_province(pid_other); state.map_state.center_map_on_province(state, state.map_state.get_selected_province()); - state.update_trade_flow.store(true, std::memory_order::release); + state.flow_map.request_update(flow_map::data_source::commodity); if(state.ui_state.province_window) { state.ui_state.province_window->impl_on_update(state); } diff --git a/src/gui/province_tiles/gui_province_tiles_tiles.hpp b/src/gui/province_tiles/gui_province_tiles_tiles.hpp index 1896d5b74..e12cd5a75 100644 --- a/src/gui/province_tiles/gui_province_tiles_tiles.hpp +++ b/src/gui/province_tiles/gui_province_tiles_tiles.hpp @@ -460,7 +460,7 @@ class tax_collector_tile : public tile_type_logic { void update_tooltip(sys::state& state, int32_t x, int32_t y, text::columnar_layout& contents, province_tile target) noexcept override { auto n = state.world.province_get_nation_from_province_ownership(target.province); - text::add_line(state, contents, "province_control_scale", text::variable_type::value, text::fp_one_place{ state.world.province_get_control_scale(target.province) }); + text::add_line(state, contents, "province_control_scale", text::variable_type::value, text::prettify_float(state.world.province_get_control_scale(target.province))); // Explains nations::update_administrative_efficiency @@ -602,9 +602,9 @@ class tax_collector_tile : public tile_type_logic { control_buffer_tobe.set(pid, control_buffer_tobe.get(pid) + change * 0.01f); if(pid == target.province) { - text::add_line(state, contents, "province_control_scale_from_state_capital", text::variable_type::value, text::fp_one_place{ change }, text::variable_type::prov, state.world.province_get_name(capital)); + text::add_line(state, contents, "province_control_scale_from_state_capital", text::variable_type::value, text::prettify_float(change), text::variable_type::prov, state.world.province_get_name(capital)); } else if(capital == target.province) { - text::add_line(state, contents, "province_control_scale_to_state_capital", text::variable_type::value, text::fp_one_place{ change }, text::variable_type::prov, state.world.province_get_name(pid)); + text::add_line(state, contents, "province_control_scale_to_state_capital", text::variable_type::value, text::prettify_float(change), text::variable_type::prov, state.world.province_get_name(pid)); } }); }); @@ -629,7 +629,7 @@ class tax_collector_tile : public tile_type_logic { state.world.for_each_province([&](auto pid) { auto total_weight = total_adjacency_weight.get(pid) + 0.00001f; - auto control_to_transfer = control_buffer_asis.get(pid) * 0.9f; + auto control_to_transfer = control_buffer_asis.get(pid) * 0.05f; state.world.province_for_each_province_adjacency(pid, [&](auto adj) { auto other = state.world.province_adjacency_get_connected_provinces(adj, 0); if(other == pid) { @@ -639,10 +639,10 @@ class tax_collector_tile : public tile_type_logic { auto mult = nations::control_shift_weight_mult(state, adj); control_buffer_tobe.set(other, control_buffer_tobe.get(other) + control_to_transfer * score * mult / total_weight); if(other == target.province) { - text::add_line(state, contents, "province_control_scale_adjacency_received", text::variable_type::value, text::fp_one_place{ control_to_transfer * score * mult / total_weight }, text::variable_type::prov, state.world.province_get_name(pid)); + text::add_line(state, contents, "province_control_scale_adjacency_received", text::variable_type::value, text::prettify_float(control_to_transfer* score* mult / total_weight), text::variable_type::prov, state.world.province_get_name(pid)); } else if(pid == target.province) { - text::add_line(state, contents, "province_control_scale_adjacency_sent", text::variable_type::value, text::fp_one_place{ control_to_transfer * score * mult / total_weight }, text::variable_type::prov, state.world.province_get_name(other)); + text::add_line(state, contents, "province_control_scale_adjacency_sent", text::variable_type::value, text::prettify_float(control_to_transfer* score* mult / total_weight), text::variable_type::prov, state.world.province_get_name(other)); } }); control_buffer_tobe.set(pid, control_buffer_tobe.get(pid) - control_to_transfer); diff --git a/src/gui/trade_dashboard.cpp b/src/gui/trade_dashboard.cpp index 889f5dfd2..9dc84366f 100644 --- a/src/gui/trade_dashboard.cpp +++ b/src/gui/trade_dashboard.cpp @@ -5788,7 +5788,7 @@ ui::message_result trade_dashboard_commodity_selector_name_t::on_lbutton_down(sy state.selected_trade_good = commodity_selector.cid; state.ui_cached_data.set_commodity(state, commodity_selector.cid); } - state.update_trade_flow.store(true, std::memory_order::release); + state.flow_map.request_update(flow_map::data_source::commodity); main.on_update(state); // END return ui::message_result::consumed; diff --git a/src/map/flow_map.cpp b/src/map/flow_map.cpp new file mode 100644 index 000000000..0c549912d --- /dev/null +++ b/src/map/flow_map.cpp @@ -0,0 +1,1007 @@ +#include "flow_map.hpp" +#include "province.hpp" +#include "province_templates.hpp" +#include "economy_stats.hpp" +#include "nations.hpp" + +namespace map { +glm::vec2 get_army_location(sys::state const& state, dcon::province_id prov_id); +glm::vec2 put_in_local(glm::vec2 new_point, glm::vec2 base_point, float size_x); +float smootherstep(float x); +} + +namespace nations { +float trade_route_control_propagation( + sys::state const& state, + ve::vectorizable_buffer const& control_buffer, + ve::vectorizable_buffer const& coastal_capital_buffer, + dcon::trade_route_id trid +); +} + +namespace flow_map { + +void reserve_graph_edge( + std::vector>>& graph, + int A, int B, int edge_index, int edges_mult +) { + if(graph[A].contains(B)) { + return; + } else { + graph[A][B] = { }; + graph[A][B].resize(edges_mult); + } +} + +void perform_negation(flow_map_data& data, int node1, int node2, int edge_index, int edges_mult) { + reserve_graph_edge(data.flow_graph, node1, node2, edge_index, edges_mult); + reserve_graph_edge(data.flow_graph, node2, node1, edge_index, edges_mult); + float forward = data.flow_graph[node1][node2][edge_index]; + float backward = data.flow_graph[node2][node1][edge_index]; + + if(forward >= backward) { + data.flow_graph[node1][node2][edge_index] = forward - backward; + data.flow_graph[node2][node1][edge_index] = 0.f; + } else { + data.flow_graph[node1][node2][edge_index] = 0.f; + data.flow_graph[node2][node1][edge_index] = backward - forward; + } +} + +void register_trade_flow(flow_map_data& data, int node1, int node2, float volume, int edge_index, int edges_mult) { + assert(node1 >= 0); + assert(node2 >= 0); + assert(volume >= 0.f); + reserve_graph_edge(data.flow_graph, node1, node2, edge_index, edges_mult); + data.flow_graph[node1][node2][edge_index] += volume; + data.node_total_in[node2][edge_index] = data.node_total_in[node2][edge_index] + volume; + data.node_total_out[node1][edge_index] = data.node_total_out[node1][edge_index] + volume; +} + +int node_index(dcon::province_id prov) { + return prov.index(); +} + +void add_path(flow_map_data& data, dcon::province_id start, std::vector& path, float volume, int edge_index, int edges_mult) { + if(path.size() > 0) { + register_trade_flow(data, node_index(start), node_index(path.back()), volume, edge_index, edges_mult); + } + for(int i = int(path.size()) - 1; i >= 0; i--) { + auto end = path[i]; + auto start_index = node_index(start); + auto end_index = node_index(end); + register_trade_flow(data, start_index, end_index, volume, edge_index, edges_mult); + start = end; + } +} +void add_path_with_negation(flow_map_data& data, dcon::province_id start, std::vector& path, float volume, int edge_index, int edges_mult) { + if(path.size() > 0) { + register_trade_flow(data, node_index(start), node_index(path.back()), volume, edge_index, edges_mult); + perform_negation(data, node_index(start), node_index(path.back()), edge_index, edges_mult); + } + for(int i = int(path.size()) - 1; i >= 0; i--) { + auto end = path[i]; + auto start_index = node_index(start); + auto end_index = node_index(end); + register_trade_flow(data, start_index, end_index, volume, edge_index, edges_mult); + perform_negation(data, start_index, end_index, edge_index, edges_mult); + start = end; + } +} + +void build_graph_commodity(sys::state const& state, flow_map_data& data, dcon::commodity_id cid) { + //flow_map_data& data = state.flow_map; + + auto edge = cid.index(); + auto count = state.world.commodity_size(); + + /* + Build the graph + */ + state.world.for_each_trade_route([&](dcon::trade_route_id trade_route) { + auto current_volume = state.world.trade_route_get_volume(trade_route, cid); + auto origin = state.world.trade_route_get_origin(trade_route); + auto target = state.world.trade_route_get_target(trade_route); + auto s_origin = state.world.market_get_zone_from_local_market(origin); + auto s_target = state.world.market_get_zone_from_local_market(target); + auto p_origin = state.world.state_instance_get_capital(s_origin); + auto p_target = state.world.state_instance_get_capital(s_target); + auto sat = state.world.market_get_actual_probability_to_buy(origin, cid); + auto absolute_volume = sat * current_volume; + if(absolute_volume < data.cutoff) { + return; + } + bool is_sea = state.world.trade_route_get_is_sea_route(trade_route); + if(is_sea) { + auto coast_origin = province::state_get_coastal_capital(state, s_origin); + auto coast_target = province::state_get_coastal_capital(state, s_target); + + auto origin_path = province::make_land_trade_path(state, p_origin, coast_origin); + add_path(data, p_origin, origin_path, absolute_volume, edge, count); + + auto sea_path = province::make_sea_trade_route_path(state, coast_origin, coast_target); + add_path(data, coast_origin, sea_path, absolute_volume, edge, count); + + auto target_path = province::make_land_trade_path(state, coast_target, p_target); + add_path(data, coast_target, target_path, absolute_volume, edge, count); + } else { + auto path = province::make_land_trade_path(state, p_origin, p_target); + add_path(data, p_origin, path, absolute_volume, edge, count); + } + }); +} + +bool common_administration(sys::state const& state, dcon::nation_id selected_nation, dcon::nation_id candidate) { + auto sphere_leader = state.world.nation_get_in_sphere_of(candidate); + auto overlord = state.world.overlord_get_ruler(state.world.nation_get_overlord_as_subject(candidate)); + auto selected_is_subject = false; + for(auto subject : state.world.nation_get_overlord_as_ruler(candidate)) { + if(subject.get_subject() == selected_nation) + selected_is_subject = true; + } + return !selected_nation + || candidate == selected_nation + || sphere_leader == selected_nation + || overlord == selected_nation + || selected_is_subject; +} + +bool common_administration(sys::state const& state, dcon::nation_id selected_nation, dcon::province_id candidate) { + return common_administration(state, selected_nation, state.world.province_get_nation_from_province_ownership(candidate)); +} + +void build_graph_administration(sys::state const& state, flow_map_data& data) { + auto target_province = state.map_state.selected_province; + auto target_nation = state.world.province_get_nation_from_province_ownership(target_province); + + auto control_buffer = state.world.province_make_vectorizable_float_buffer(); + state.world.execute_serial_over_province([&](auto ids) { + control_buffer.set(ids, state.world.province_get_control_scale(ids)); + }); + auto control_buffer_out = state.world.province_make_vectorizable_float_buffer(); + state.world.execute_serial_over_province([&](auto ids) { + control_buffer_out.set(ids, state.world.province_get_control_scale(ids)); + }); + + auto coastal_capital_buffer = ve::vectorizable_buffer(state.world.state_instance_size()); + state.world.execute_parallel_over_state_instance([&](auto ids) { + ve::apply([&](auto sid) { + coastal_capital_buffer.set(sid, province::state_get_coastal_capital(state, sid)); + }, ids); + }); + + state.world.for_each_trade_route([&](dcon::trade_route_id trid) { + auto distance = state.world.trade_route_get_distance_km(trid); + auto A_market = state.world.trade_route_get_origin(trid); + auto B_market = state.world.trade_route_get_target(trid); + auto A_state_instance = state.world.market_get_zone_from_local_market(A_market); + auto B_state_instance = state.world.market_get_zone_from_local_market(B_market); + auto capital_A = state.world.state_instance_get_capital(A_state_instance); + auto capital_B = state.world.state_instance_get_capital(B_state_instance); + if(!common_administration(state, target_nation, capital_A) || !common_administration(state, target_nation, capital_B)) { + return; + } + + auto shift = nations::trade_route_control_propagation(state, control_buffer, coastal_capital_buffer, trid); + bool is_sea = state.world.trade_route_get_is_sea_route(trid); + + if(is_sea) { + auto coast_origin = coastal_capital_buffer.get(A_state_instance); + auto coast_target = coastal_capital_buffer.get(B_state_instance); + if(shift < 0) { + auto origin_path = province::make_land_trade_path(state, capital_A, coast_origin); + add_path(data, capital_A, origin_path, -shift, 0, 1); + + auto sea_path = province::make_sea_trade_route_path(state, coast_origin, coast_target); + add_path(data, coast_origin, sea_path, -shift, 0, 1); + + auto target_path = province::make_land_trade_path(state, coast_target, capital_B); + add_path(data, coast_target, target_path, -shift, 0, 1); + } else { + auto origin_path = province::make_land_trade_path(state, capital_B, coast_target); + add_path(data, capital_B, origin_path, shift, 0, 1); + + auto sea_path = province::make_sea_trade_route_path(state, coast_target, coast_origin); + add_path(data, coast_target, sea_path, shift, 0, 1); + + auto target_path = province::make_land_trade_path(state, coast_origin, capital_A); + add_path(data, coast_origin, target_path, shift, 0, 1); + } + } else { + if(shift < 0) { + auto path = province::make_land_trade_path(state, capital_A, capital_B); + add_path(data, capital_A, path, -shift, 0, 1); + } else { + auto path = province::make_land_trade_path(state, capital_B, capital_A); + add_path(data, capital_B, path, shift, 0, 1); + } + } + + control_buffer_out.set(capital_A, control_buffer_out.get(capital_A) + shift); + control_buffer_out.set(capital_B, control_buffer_out.get(capital_B) - shift); + }); + + state.world.for_each_state_instance([&](auto sid) { + auto capital = state.world.state_instance_get_capital(sid); + if(!common_administration(state, target_nation, capital)) { + return; + } + province::for_each_province_in_state_instance(state, sid, [&](auto pid) { + auto change = (control_buffer.get(capital) - control_buffer.get(pid)) * 0.01f; + + if(change < 0) { + auto path = province::make_land_trade_path(state, pid, capital); + add_path(data, pid, path, -change, 0, 1); + } else { + auto path = province::make_land_trade_path(state, capital, pid); + add_path(data, capital, path, change, 0, 1); + } + + control_buffer_out.set(pid, control_buffer_out.get(pid) + change); + control_buffer_out.set(capital, control_buffer_out.get(capital) - change); + }); + }); + + state.world.execute_serial_over_province([&](auto ids) { + control_buffer.set(ids, control_buffer_out.get(ids)); + }); + + auto total_adjacency_weight = state.world.province_make_vectorizable_float_buffer(); + + state.world.for_each_province_adjacency([&](auto paid) { + auto A = state.world.province_adjacency_get_connected_provinces(paid, 0); + auto B = state.world.province_adjacency_get_connected_provinces(paid, 1); + if(!common_administration(state, target_nation, A) || !common_administration(state, target_nation, B)) { + return; + } + auto mult = nations::control_shift_weight_mult(state, paid); + auto weight_A = nations::desire_score_province(state, A) * mult; + auto weight_B = nations::desire_score_province(state, B) * mult; + auto old_A = total_adjacency_weight.get(A); + auto old_B = total_adjacency_weight.get(B); + total_adjacency_weight.set(B, old_B + weight_A); + total_adjacency_weight.set(A, old_A + weight_B); + }); + + state.world.for_each_province([&](auto pid) { + if(!common_administration(state, target_nation, pid)) { + return; + } + auto total_weight = total_adjacency_weight.get(pid) + 0.00001f; + auto control_to_transfer = control_buffer.get(pid) * 0.05f; + state.world.province_for_each_province_adjacency(pid, [&](auto adj) { + auto other = state.world.province_adjacency_get_connected_provinces(adj, 0); + if(other == pid) { + other = state.world.province_adjacency_get_connected_provinces(adj, 1); + } + if(!common_administration(state, target_nation, other)) { + return; + } + auto score = nations::desire_score_province(state, other); + auto mult = nations::control_shift_weight_mult(state, adj); + + auto shift = control_to_transfer * score * mult / total_weight; + std::vector path = { other }; + add_path_with_negation(data, pid, path, shift, 0, 1); + }); + }); +} + +void convert_balance_to_probabilities(flow_map_data& data, int sea_node_index, int edges_mult) { + //std::vector total_out {}; + //total_out.resize(edges_mult); + float total_out_all_edges = 0.f; + data.edge_layer_probability.resize(edges_mult); + + for(int edge = 0; edge < edges_mult; ++edge) { + float total_out = 0.f; + for(size_t i = 0; i < data.node_total_out.size(); ++i) { + auto local_balance = data.node_total_out[i][edge] - data.node_total_in[i][edge]; + if(local_balance > 0) { + total_out += local_balance; + } + } + if(total_out == 0.f) { + for(size_t i = 0; i < data.node_total_out.size(); ++i) { + data.node_probability_create[i][edge] = 0.f; + } + } else { + for(size_t i = 0; i < data.node_total_out.size(); ++i) { + auto local_balance = std::max(0.f, data.node_total_out[i][edge] - data.node_total_in[i][edge]); + data.node_probability_create[i][edge] = local_balance / total_out; + } + } + + data.edge_layer_probability[edge] = total_out; + total_out_all_edges += total_out; + + for(size_t i = 0; i < data.node_total_out.size(); ++i) { + float total_volume_out = std::max(0.f, data.node_total_in[i][edge] - data.node_total_out[i][edge] - 0.01f); + if(i >= (size_t)sea_node_index) { + total_volume_out = 0.f; + } + + for(auto const& [target_index, volume] : data.flow_graph[i]) { + total_volume_out += volume[edge]; + } + + if(total_volume_out > 0.f) { + for(auto const& [target_index, volume] : data.flow_graph[i]) { + reserve_graph_edge(data.particle_next_node_probability, (int)i, target_index, edge, edges_mult); + data.particle_next_node_probability[i][target_index][edge] = volume[edge] / total_volume_out; + } + } else { + for(auto const& [target_index, volume] : data.flow_graph[i]) { + reserve_graph_edge(data.particle_next_node_probability, (int)i, target_index, edge, edges_mult); + data.particle_next_node_probability[i][target_index][edge] = 0.f; + } + } + } + } + + if(total_out_all_edges > 0.f) { + for(int edge = 0; edge < edges_mult; ++edge) { + data.edge_layer_probability[edge] /= total_out_all_edges; + } + } +} + +void clear_vertices(sys::state& state) { + map::display_data& map_data = state.map_state.map_data; + map_data.trade_flow_vertices.clear(); + map_data.trade_flow_arrow_counts.clear(); + map_data.trade_flow_arrow_starts.clear(); +} + +void convert_graph_to_vertices(sys::state& state, int edge) { + bool aggregate = edge == -1; + + flow_map_data& data = state.flow_map; + map::display_data& map_data = state.map_state.map_data; + auto& graph = data.flow_graph; + + map_data.trade_flow_vertices.clear(); + map_data.trade_flow_arrow_counts.clear(); + map_data.trade_flow_arrow_starts.clear(); + + auto size_x = float(map_data.size_x); + auto size_y = float(map_data.size_y); + + std::map visited; + state.world.for_each_province([&](dcon::province_id origin) { visited[origin.index()] = false; }); + + float the_most_fat_route = 0.f; + + std::map> graph_incoming; + state.world.for_each_province([&](dcon::province_id origin) { + graph_incoming[origin.index()] = { }; + }); + // todo: use the most important node as previous by storing "volume" and updating previous only when volume gets larger + std::map previous; + state.world.for_each_province([&](dcon::province_id origin) { + for(auto& [key, value] : graph[origin.index()]) { + previous[key] = origin.index(); + auto replace = 0.f; + if(aggregate) { + for(auto val : value) { + replace += val; + } + } else { + replace = value[edge]; + } + + graph_incoming[key][origin.index()] = replace; + the_most_fat_route = std::max(the_most_fat_route, replace); + } + }); + + std::map> graph_max_incoming; + state.world.for_each_province([&](dcon::province_id origin) { + graph_max_incoming[origin.index()] = { }; + }); + + auto is_sea = [&](dcon::province_id x) { + return x.value >= state.province_definitions.first_sea_province.value; + }; + + state.world.for_each_province([&](dcon::province_id origin) { + if(previous.contains(origin.index())) { + auto total_outgoing = 0.f; + auto total_incoming = 0.f; + + for(auto const& [index, volume] : graph[origin.index()]) { + total_outgoing += volume[edge]; + } + + for(auto const& [index, volume] : graph_incoming[origin.index()]) { + total_incoming += volume; + } + + auto left_outgoing = total_outgoing; + auto left_incoming = total_incoming; + + for(auto const& [target_index, target_volume] : graph[origin.index()]) { + for(auto const& [source_index, source_volume] : graph_incoming[origin.index()]) { + auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; + auto source = dcon::province_id{ dcon::province_id::value_base_t(source_index) }; + + if(source == target) { + continue; + } + + // if land->[port]->sea - abort + // if sea->[port]->land - abort + if(is_sea(source) && !is_sea(origin) && !is_sea(target))continue; + if(!is_sea(source) && !is_sea(origin) && is_sea(target))continue; + + auto volume_start = + source_volume; + auto volume_end = + volume_start + * data.particle_next_node_probability[origin.index()][target_index][edge]; + + left_incoming -= volume_end; + left_outgoing -= volume_end; + + auto found = graph_max_incoming[target_index].find(origin.index()); + if(found == graph_max_incoming[target_index].end()) + graph_max_incoming[target_index][origin.index()] = volume_end; + else if(graph_max_incoming[target_index][origin.index()] < volume_end) + graph_max_incoming[target_index][origin.index()] = volume_end; + } + } + + if(left_outgoing > 0.001f) { + for(auto const& [target_index, target_volume] : graph[origin.index()]) { + auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; + auto volume = target_volume[edge] * left_outgoing / total_outgoing; + + auto found = graph_max_incoming[target_index].find(origin.index()); + if(found == graph_max_incoming[target_index].end()) + graph_max_incoming[target_index][origin.index()] = volume; + else if(graph_max_incoming[target_index][origin.index()] < volume) + graph_max_incoming[target_index][origin.index()] = volume; + } + } + } + }); + + + std::map distance_field; + std::vector to_visit; + size_t current_index_to_visit = 0; + state.world.for_each_province([&](dcon::province_id origin) { + if(!visited[origin.index()]) { + to_visit.clear(); + to_visit.push_back(origin.index()); + current_index_to_visit = 0; + distance_field[origin.index()] = 0.f; + + while(current_index_to_visit < to_visit.size()) { + auto current = to_visit[current_index_to_visit]; + auto prev_it = previous.find(current); + if(prev_it != previous.end() && !visited[prev_it->second]) { + auto edge_volume = graph[prev_it->second][current][edge]; + auto width = std::min(std::sqrt(std::abs(edge_volume)) / data.cutoff, 5.f) * 1000.f; + distance_field[prev_it->second] = distance_field[current] - province::direct_distance( + state, + dcon::province_id{ (dcon::province_id::value_base_t)(current) }, + dcon::province_id{ (dcon::province_id::value_base_t)(prev_it->second) } + ) / width; + to_visit.push_back(prev_it->second); + visited[prev_it->second] = true; + } + + for(auto const& [target_index, volume] : graph[origin.index()]) { + if(!visited[target_index]) { + auto edge_volume = graph[current][target_index]; + auto width = std::min(std::sqrt(std::abs(edge_volume[edge])) / data.cutoff, 5.f) * 1000.f; + distance_field[target_index] = distance_field[current] + province::direct_distance( + state, + dcon::province_id{ (dcon::province_id::value_base_t)(current) }, + dcon::province_id{ (dcon::province_id::value_base_t)(target_index) } + ) / width; + to_visit.push_back(target_index); + visited[target_index] = true; + } + } + + current_index_to_visit++; + } + }; + }); + + // now we are building vertices + + auto volume_to_width = [&](float volume) { + return std::abs(volume) / std::max(0.05f, the_most_fat_route) * 40000.f; + }; + + std::map vertices_built; + state.world.for_each_province([&](dcon::province_id origin) { vertices_built[origin.index()] = false; }); + + auto build_bezier = [&]( + glm::vec2 start, + glm::vec2 start_tangent, + float start_width, + glm::vec2 end, + glm::vec2 end_tangent, + float end_width, + float& distance + ) { + auto s = glm::vec2(size_x, size_y); + auto old_size = map_data.trade_flow_vertices.size(); + map_data.trade_flow_arrow_starts.push_back(GLint(old_size)); + auto start_normal = glm::vec2(-start_tangent.y, start_tangent.x); + auto norm_pos = start; // glm::vec2(size_x, size_y); + map_data.trade_flow_vertices.emplace_back(map::textured_line_with_width_vertex{ + norm_pos, start_normal, + 0.f, distance, start_width + }); + map_data.trade_flow_vertices.emplace_back(map::textured_line_with_width_vertex{ + norm_pos, -start_normal, + 1.f, distance, start_width + }); + // final step + add_bezier_to_buffer_variable_width( + map_data.trade_flow_vertices, + start * s, map::put_in_local(end * s, start * s, size_x), + start_tangent, end_tangent, + 1.0f, + false, + size_x, size_y, + 40, + distance, + start_width, end_width, end_width + ); + + map_data.trade_flow_arrow_counts.push_back( + GLsizei(map_data.trade_flow_vertices.size() - old_size) + ); + }; + + auto build_bezier_adjacency = [&]( + dcon::province_adjacency_id hint, bool forward, + bool first_half, + float start_width, + float end_width, + float& distance + ) { + auto start = state.map_state.map_data.railroad_starts[hint.index()]; + auto count = state.map_state.map_data.railroad_counts[hint.index()]; + auto middle = (count / 8) * 4 - 2; + + if(middle <= 0) { + return; + } + + auto idx = start; + auto bound_left = idx; + auto bound_right = idx + middle; + if(forward && first_half) { + idx = start; + bound_left = start; + bound_right = start + middle; + } else if(!forward && first_half) { + idx = start + middle; + bound_left = start; + bound_right = start + middle; + } else if(forward && !first_half) { + idx = start + middle; + bound_left = start + middle; + bound_right = start + count - 2; + } else if(!forward && !first_half) { + idx = start + count - 2; + bound_left = start + middle; + bound_right = start + count - 2; + } + auto sign = 1; + auto step = 16; + if(!forward) { + step = -16; + sign = -1; + } + + auto initial_idx = idx; + + while(bound_left <= idx && idx <= bound_right) { + glm::vec2 current_position{ }; + glm::vec2 current_normal{ }; + glm::vec2 next_position{ }; + glm::vec2 next_normal{ }; + + current_position = state.map_state.map_data.railroad_vertices[idx].position_; + current_normal = state.map_state.map_data.railroad_vertices[idx].normal_direction_; + + next_position = state.map_state.map_data.railroad_vertices[std::clamp(idx + step, bound_left, bound_right)].position_; + next_normal = state.map_state.map_data.railroad_vertices[std::clamp(idx + step, bound_left, bound_right)].normal_direction_; + + if(!forward) { + current_normal = -current_normal; + next_normal = -next_normal; + } + + auto current_tangent = glm::vec2(current_normal.y, -current_normal.x); + auto next_tangent = glm::vec2(next_normal.y, -next_normal.x); + + + float t = abs(float(idx - initial_idx)) / float(middle); + auto local_width = start_width; + if(t < 0.5f) { + auto a = map::smootherstep(t * 2.f); + auto b = 1.f - a; + local_width = b * start_width + a * end_width; + } else { + auto a = map::smootherstep((t - 0.5f) * 2.f); + auto b = 1.f - a; + local_width = b * end_width + a * end_width; + } + if(current_position != next_position) { + build_bezier( + current_position, current_tangent, start_width, + next_position, next_tangent, local_width, + distance + ); + } + idx = idx + step; + start_width = local_width; + } + }; + + auto build_bezier_hint_both = [&]( + dcon::province_adjacency_id hint_start, bool forward_start, + float start_width, + dcon::province_adjacency_id hint_end, bool forward_end, + float end_width, + float& distance, + int step_size + ) { + auto start_start = state.map_state.map_data.railroad_starts[hint_start.index()]; + auto start_count = state.map_state.map_data.railroad_counts[hint_start.index()]; + auto start_middle = (start_count / 8) * 4 - 2; + auto start_idx = start_start + start_middle; + int start_step = step_size * 8; + if(!forward_start) { + start_step = -step_size * 8; + } + if(start_middle <= 0) { + return; + } + + auto end_start = state.map_state.map_data.railroad_starts[hint_end.index()]; + auto end_count = state.map_state.map_data.railroad_counts[hint_end.index()]; + auto end_middle = (end_count / 8) * 4 - 2; + auto end_idx = end_start; + int end_step = step_size * 8; + if(!forward_end) { + end_idx = end_start + end_count - 2; + end_step = -step_size * 8; + } + if(end_middle <= 0) { + return; + } + + while((forward_end && end_idx < end_start + end_middle) || (!forward_end && end_idx > end_start + end_middle)) { + glm::vec2 current_position { }; + glm::vec2 current_normal{ }; + glm::vec2 next_position{ }; + glm::vec2 next_normal{ }; + + if(start_idx < start_start + start_count && start_idx >= start_start) { + current_position = state.map_state.map_data.railroad_vertices[start_idx].position_; + current_normal = state.map_state.map_data.railroad_vertices[start_idx].normal_direction_; + if(!forward_start) { + current_normal = -current_normal; + } + } else { + current_position = state.map_state.map_data.railroad_vertices[end_idx].position_; + current_normal = state.map_state.map_data.railroad_vertices[end_idx].normal_direction_; + if(!forward_end) { + current_normal = -current_normal; + } + } + if(start_idx + start_step < start_start + start_count && start_idx + start_step >= start_start) { + next_position = state.map_state.map_data.railroad_vertices[start_idx + start_step].position_; + next_normal = state.map_state.map_data.railroad_vertices[start_idx + start_step].normal_direction_; + if(!forward_start) { + next_normal = -next_normal; + } + } else { + auto clamp_left = end_start; + auto clamp_right = end_start + end_middle; + if(!forward_end) { + clamp_left = end_start + end_middle; + clamp_right = end_start + end_count - 2; + } + next_position = state.map_state.map_data.railroad_vertices[std::clamp(end_idx + end_step, clamp_left, clamp_right)].position_; + next_normal = state.map_state.map_data.railroad_vertices[std::clamp(end_idx + end_step, clamp_left, clamp_right)].normal_direction_; + + if(!forward_end) { + next_normal = -next_normal; + } + } + + auto current_tangent = glm::vec2(current_normal.y, -current_normal.x); + auto next_tangent = glm::vec2(next_normal.y, -next_normal.x); + + float t1 = abs(float(start_idx - start_start)) / float(start_count); + float t2 = abs(float(end_idx - end_start)) / float(end_count); + float t = t1 + t2; + + auto local_width = start_width; + if(t < 0.5f) { + auto a = map::smootherstep(t * 2.f); + auto b = 1.f - a; + local_width = b * start_width + a * end_width; + } else { + auto a = map::smootherstep((t - 0.5f) * 2.f); + auto b = 1.f - a; + local_width = b * end_width + a * end_width; + } + + if(start_idx + start_step < start_start + start_count && start_idx + start_step >= start_start) { + start_idx += start_step; + } else if(start_idx < start_start + start_count && start_idx >= start_start) { + start_idx += start_step; + end_idx += end_step; + } else { + end_idx += end_step; + } + if(current_position != next_position) { + build_bezier( + current_position, current_tangent, start_width, + next_position, next_tangent, local_width, + distance + ); + } + start_width = local_width; + } + }; + + + state.world.for_each_province([&](dcon::province_id origin) { + if(previous.contains(origin.index())) { + auto total_outgoing = 0.f; + auto total_incoming = 0.f; + + for(auto const& [index, volume] : graph[origin.index()]) { + total_outgoing += volume[edge]; + } + + for(auto const& [index, volume] : graph_incoming[origin.index()]) { + total_incoming += volume; + } + + auto left_outgoing = total_outgoing; + auto left_incoming = total_incoming; + + for(auto const& [target_index, target_volume] : graph[origin.index()]) { + for(auto const& [source_index, source_volume] : graph_incoming[origin.index()]) { + glm::vec2 current_pos = data.node_position[origin.index()]; + + auto volume_start = + source_volume; + auto volume_end = + volume_start + * data.particle_next_node_probability[origin.index()][target_index][edge]; + + auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; + glm::vec2 next_pos = map::put_in_local(data.node_position[target_index], current_pos, size_x); + + auto source = dcon::province_id{ dcon::province_id::value_base_t(source_index) }; + glm::vec2 prev_pos = map::put_in_local(data.node_position[source_index], current_pos, size_x); + + if(source_index == target_index) { + // model of probabilistic movement is not perfect, so we have to ignore something when we attempt to move in a loop + left_incoming -= volume_end; + left_outgoing -= volume_end; + continue; + } + + auto adj = state.world.get_province_adjacency_by_province_pair(source, origin); + auto adj2 = state.world.get_province_adjacency_by_province_pair(origin, target); + + // by default: connect centers of the segments between midpoints of provinces and use their tangents as start and end tangents + + glm::vec2 tangent_start = glm::normalize(current_pos - prev_pos); + glm::vec2 tangent_end = glm::normalize(next_pos - current_pos); + glm::vec2 start = (current_pos + prev_pos) / 2.f; + glm::vec2 end = (current_pos + next_pos) / 2.f; + + left_incoming -= volume_end; + left_outgoing -= volume_end; + + if(volume_end == 0.f || volume_start == 0.f) { + continue; + } + + auto start_width = volume_to_width(graph_max_incoming[origin.index()][source_index]); + if(start_width < 50.f) { + continue; + } + if(volume_to_width(volume_end) < 50.f) { + continue; + } + + // finally + float distance = distance_field[source_index]; + auto area = state.map_state.map_data.province_area_km2[province::to_map_id(origin)]; + + build_bezier_hint_both( + adj, state.world.province_adjacency_get_connected_provinces(adj, 0) != source, start_width, + adj2, state.world.province_adjacency_get_connected_provinces(adj2, 0) != origin, volume_to_width(volume_end), + distance, 1 + (int)(area / 10000.f) + ); + } + } + + if(left_incoming > 0.001f) { + // here we treat the case when some volume in was not matched with volume out + // in this case we assume that the remaining volume is "consumed" in the middle of the province + for(auto const& [source_index, source_volume] : graph_incoming[origin.index()]) { + auto distance = distance_field[source_index]; + auto source = dcon::province_id{ dcon::province_id::value_base_t(source_index) }; + + //source -> origin + auto adj = state.world.get_province_adjacency_by_province_pair(source, origin); + + auto volume_start = source_volume * left_incoming / total_incoming; + auto volume_end = volume_start; + auto width_start = volume_to_width(volume_start); + auto width_end = volume_to_width(volume_end); + if(width_start < 50.f || width_end < 50.f) { + continue; + } + + /* + Note: railroads are moving into opposite direction to adjacencies. + We want to move from source to origin. + So if the origin of adj. is source, we want to move "backward" + Also we want to get the half of adj. closer to origin. + So if we are moving forward, we want to take later part. + */ + auto forward = state.world.province_adjacency_get_connected_provinces(adj, 0) != source; + + build_bezier_adjacency( + adj, forward, !forward, width_start, + width_end, distance + ); + } + } + + if(left_outgoing > 0.001f) { + for(auto const& [target_index, target_volume] : graph[origin.index()]) { + auto distance = distance_field[origin.index()]; + auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; + + //origin -> target + auto adj = state.world.get_province_adjacency_by_province_pair(origin, target); + + auto volume_start = target_volume[edge] * left_outgoing / total_outgoing; + auto volume_end = volume_start; + auto width_start = volume_to_width(volume_start); + auto width_end = volume_to_width(volume_end); + if(width_start < 50.f || width_end < 50.f) { + continue; + } + + auto forward = state.world.province_adjacency_get_connected_provinces(adj, 0) != origin; + + build_bezier_adjacency( + adj, forward, forward, width_start, + width_end, distance + ); + } + } + } + }); + + if(!map_data.trade_flow_vertices.empty()) { + glBindBuffer(GL_ARRAY_BUFFER, map_data.vbo_array[map_data.vo_trade_flow]); + glBufferData( + GL_ARRAY_BUFFER, + sizeof(map::textured_line_with_width_vertex) + * map_data.trade_flow_vertices.size(), + map_data.trade_flow_vertices.data(), + GL_STATIC_DRAW + ); + } +} + +void reset_particles(flow_map_data& data) { + data.flow_particles_positions.clear(); + data.flow_particles_content.clear(); + for(int i = 0; i < data.amount_of_particles; i++) { + flow_particle p{ + .position_ = { }, + .target_ = { }, + .graph_node_current = -1, + .graph_node_prev = -1, + .graph_node_next = -1 + }; + data.flow_particles_positions.push_back(p); + data.flow_particles_content.push_back(0); + } +} + +void update(sys::state& state) { + flow_map_data& data = state.flow_map; + map::display_data& map_data = state.map_state.map_data; + + if(data.update_requested.load(std::memory_order::acquire)) { + data.flow_graph.clear(); + data.particle_next_node_probability.clear(); + data.node_probability_create.clear(); + + if(data.source == data_source::commodity) { + data.cutoff = 0.005f; + data.flow_graph.resize(state.world.province_size()); + data.particle_next_node_probability.resize(state.world.province_size()); + data.node_position.resize(state.world.province_size()); + data.node_total_in.resize(state.world.province_size()); + data.node_total_out.resize(state.world.province_size()); + data.node_probability_create.resize(state.world.province_size()); + state.world.for_each_province([&](auto pid) { + data.node_position[pid.index()] = map::get_army_location(state, pid); + data.node_total_out[pid.index()].resize(state.world.commodity_size()); + data.node_total_in[pid.index()].resize(state.world.commodity_size()); + data.node_probability_create[pid.index()].resize(state.world.commodity_size()); + }); + + state.world.for_each_commodity([&](auto item) { + build_graph_commodity(state, data, item); + //convert_graph_to_vertices(state); + }); + convert_balance_to_probabilities(data, state.province_definitions.first_sea_province.index(), state.world.commodity_size()); + + if(state.user_settings.trade_particles_count == 0) { + data.amount_of_particles = 0; + } else if(state.user_settings.trade_particles_count == 1) { + data.amount_of_particles = 1000; + } else if(state.user_settings.trade_particles_count == 2) { + data.amount_of_particles = 2000; + } else if(state.user_settings.trade_particles_count == 3) { + data.amount_of_particles = 4000; + } else if(state.user_settings.trade_particles_count == 4) { + data.amount_of_particles = 8000; + } else if(state.user_settings.trade_particles_count == 5) { + data.amount_of_particles = 16000; + } + + if(state.selected_trade_good) { + convert_graph_to_vertices(state, state.selected_trade_good.index()); + } + + reset_particles(data); + } else if(data.source == data_source::administration) { + data.cutoff = 0.005f; + data.flow_graph.resize(state.world.province_size()); + data.particle_next_node_probability.resize(state.world.province_size()); + data.node_position.resize(state.world.province_size()); + data.node_total_in.resize(state.world.province_size()); + data.node_total_out.resize(state.world.province_size()); + data.node_probability_create.resize(state.world.province_size()); + state.world.for_each_province([&](auto pid) { + data.node_position[pid.index()] = map::get_army_location(state, pid); + data.node_total_out[pid.index()].resize(1); + data.node_total_in[pid.index()].resize(1); + data.node_probability_create[pid.index()].resize(1); + }); + build_graph_administration(state, data); + convert_balance_to_probabilities(data, state.province_definitions.first_sea_province.index(), 1); + convert_graph_to_vertices(state, 0); + + data.amount_of_particles = 4000; + + reset_particles(data); + } else if(data.source == data_source::none) { + data.node_position.clear(); + data.node_total_in.clear(); + data.node_total_out.clear(); + clear_vertices(state); + } + data.update_requested.store(false, std::memory_order_release); + } +} + +} diff --git a/src/map/flow_map.hpp b/src/map/flow_map.hpp new file mode 100644 index 000000000..c812b852e --- /dev/null +++ b/src/map/flow_map.hpp @@ -0,0 +1,57 @@ +#pragma once +#include +#include + +namespace flow_map { + +enum data_source { + external, + commodity, + administration, + none +}; + +struct flow_particle { + glm::vec2 position_; + glm::vec2 target_; + int graph_node_current; + int graph_node_prev = -1; + int graph_node_next; + int adj_index = -1; + int adj_count = 0; + int adj_direction = 1; + std::array wagon_positions{}; + + dcon::unit_type_id ship_model { }; +}; + +struct flow_map_data { + std::atomic update_requested = true; + data_source source = data_source::commodity; + + float cutoff = 0.f; + + // particles + int amount_of_particles; + std::vector flow_particles_positions {}; + std::vector flow_particles_content {}; + + std::vector>> flow_graph {}; + std::vector>> particle_next_node_probability{}; + std::vector> node_probability_create{}; + std::vector edge_layer_probability{}; + std::vector> node_total_out{}; + std::vector> node_total_in{}; + + std::vector node_position{}; + + void request_update(data_source s) { + source = s; + update_requested.store(true, std::memory_order::release); + } +}; + +void update(sys::state& state); + +} + diff --git a/src/map/map.cpp b/src/map/map.cpp index 6c81f759a..14f343a26 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -49,7 +49,7 @@ glm::vec2 get_navy_location(sys::state& state, dcon::province_id prov_id) { return get_port_location(state, prov_id); } -glm::vec2 get_army_location(sys::state& state, dcon::province_id prov_id) { +glm::vec2 get_army_location(sys::state const& state, dcon::province_id prov_id) { return state.world.province_get_mid_point(prov_id); } } @@ -1538,7 +1538,7 @@ void display_data::render( } // trade flow - if(state.selected_trade_good && !trade_flow_vertices.empty()) { + if(!trade_flow_vertices.empty()) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textures[texture_arrow]); glActiveTexture(GL_TEXTURE3); @@ -1551,10 +1551,20 @@ void display_data::render( glBindVertexArray(vao_array[vo_trade_flow]); glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_trade_flow]); glMultiDrawArrays(GL_TRIANGLE_STRIP, trade_flow_arrow_starts.data(), trade_flow_arrow_counts.data(), GLsizei(trade_flow_arrow_starts.size())); + } + { // trade particles if(state.user_settings.graphics_mode != sys::graphics_mode::ugly) { load_shader(shader_map_sprite); + auto& flow_data = state.flow_map; + + auto gfx_ships = state.ui_defs.gui[ + state.ui_state.defs_by_name.find( + state.lookup_key("gfx_storage_unit_types") + )->second.definition + ].data.image.gfx_object; + auto& gfx_def_ships = state.ui_defs.gfx[gfx_ships]; auto gfx_id = state.ui_defs.gui[ @@ -1562,165 +1572,227 @@ void display_data::render( state.lookup_key("gfx_storage_commodity") )->second.definition ].data.image.gfx_object; - auto& gfx_def = state.ui_defs.gfx[gfx_id]; - auto frame = state.world.commodity_get_icon(state.selected_trade_good); - auto texture_handle = ogl::get_texture_handle(state, gfx_def.primary_texture_handle, gfx_def.is_partially_transparent()); - - const float speed = 0.5f; + if(flow_data.source == flow_map::administration) { + gfx_id = state.ui_defs.gui[ + state.ui_state.defs_by_name.find( + state.lookup_key("gfx_storage_pop_types") + )->second.definition + ].data.image.gfx_object; + } + auto& gfx_def = state.ui_defs.gfx[gfx_id]; + const float base_speed = 0.25f; bool spawned_something = false; + int attempts_to_spawn = 50; - { - for(size_t i = 0; i < trade_particles_positions.size(); i++) { - auto& p = trade_particles_positions[i]; - - // update movement - if(p.trade_graph_node_current != -1 && p.trade_graph_node_next != -1) { - if(p.adj_index != -1) { - auto time_left = 1.f; - while(time_left > 0.f) { - auto actual_target_offset = railroad_starts[p.adj_index] + p.adj_count; - auto actual_target = railroad_vertices[actual_target_offset].position_ * glm::vec2(float(size_x), float(size_y)); - auto direction = actual_target - p.position_; - auto length = float(glm::length(direction)); - if(length < speed * 2) { - p.adj_count += p.adj_direction; - if(p.adj_count >= railroad_counts[p.adj_index] || p.adj_count < 0) { - p.trade_graph_node_prev = p.trade_graph_node_current; - p.trade_graph_node_current = p.trade_graph_node_next; - p.trade_graph_node_next = -1; - p.adj_index = -1; - break; - } - time_left -= length / (speed * 2); - } else { - for(int vagon = (int)p.vagon_positions.size() - 1; vagon > 0; vagon--) { - p.vagon_positions[vagon] = p.vagon_positions[vagon - 1]; - } - p.vagon_positions[0] = p.position_; - p.position_ += direction / length * speed; - time_left -= 1.f; + for(size_t i = 0; i < flow_data.flow_particles_positions.size(); i++) { + auto& p = flow_data.flow_particles_positions[i]; + auto edge = flow_data.flow_particles_content[i]; + + for(int wagon = (int)p.wagon_positions.size() - 1; wagon > 0; wagon--) { + p.wagon_positions[wagon] = p.wagon_positions[wagon - 1]; + } + p.wagon_positions[0] = p.position_; + + // update movement + if(p.graph_node_current != -1 && p.graph_node_next != -1) { + auto speed = base_speed; + if( + p.graph_node_current >= state.province_definitions.first_sea_province.index() + || p.graph_node_next >= state.province_definitions.first_sea_province.index() + ) { + speed = speed * 4.f; + } + if(p.adj_index != -1) { + auto time_left = 1.f; + while(time_left > 0.f) { + auto actual_target_offset = railroad_starts[p.adj_index] + p.adj_count; + auto actual_target = railroad_vertices[actual_target_offset].position_ * glm::vec2(float(size_x), float(size_y)); + auto direction = actual_target - p.position_; + auto length = float(glm::length(direction)); + if(length < speed * 2 || length > size_x + size_y) { + p.adj_count += p.adj_direction; + if(p.adj_count >= railroad_counts[p.adj_index] || p.adj_count < 0) { + p.graph_node_prev = p.graph_node_current; + p.graph_node_current = p.graph_node_next; + p.graph_node_next = -1; + p.adj_index = -1; + break; } - } + time_left -= length / (speed * 2); } else { - auto direction = p.target_ - p.position_; - auto length = float(glm::length(direction)); - if(length < speed * 2) { - p.trade_graph_node_prev = p.trade_graph_node_current; - p.trade_graph_node_current = p.trade_graph_node_next; - p.trade_graph_node_next = -1; - } else { - for(int vagon = (int)p.vagon_positions.size() - 1; vagon > 0; vagon--) { - p.vagon_positions[vagon] = p.vagon_positions[vagon - 1]; - } - p.vagon_positions[0] = p.position_; - p.position_ += direction / length * speed; - } + p.position_ += direction / length * speed; + time_left -= 1.f; + } + } + } else { + auto direction = p.target_ - p.position_; + auto length = float(glm::length(direction)); + if(length < speed * 2 || length > size_x + size_y) { + p.graph_node_prev = p.graph_node_current; + p.graph_node_current = p.graph_node_next; + p.graph_node_next = -1; + } else { + p.position_ += direction / length * speed; } } + } - // choose target - if(p.trade_graph_node_current != -1 && p.trade_graph_node_next == -1) { - // choose next target according to probability - // use time as random engine for simplicity - auto random = fmod(sin(time_counter * 971641.5397643) + 1.f, 1.f); + // choose target + if(p.graph_node_current != -1 && p.graph_node_next == -1 && edge != -1) { + // choose next target according to probability + // use time as random engine for simplicity + auto random = fmod(sin(i * time_counter * 971641.5397643) + 1.f, 1.f); - int target = -1; + int target = -1; - auto accumulated = 0.f; - for(auto const& [candidate, probability] : particle_next_node_probability[p.trade_graph_node_current]) { - accumulated += probability; - // prevent trivial loops - if(random < accumulated && candidate != p.trade_graph_node_prev) { - target = candidate; - break; - } + auto accumulated = 0.f; + for(auto const& [candidate, probability] : flow_data.particle_next_node_probability[p.graph_node_current]) { + accumulated += probability[edge]; + // prevent trivial loops + if(random < accumulated && candidate != p.graph_node_prev) { + target = candidate; + break; } + } - // moving to itself or having no paths to go out implies deletion - if(target == -1 || target == p.trade_graph_node_current) { - p.trade_graph_node_current = -1; - } else { - p.trade_graph_node_next = target; - if(target >= (int)state.world.province_size()) { - target -= state.world.province_size(); - } - p.target_ = put_in_local(trade_node_position[target], p.position_, (float)size_x); - - auto current = p.trade_graph_node_current; - if(current >= (int)state.world.province_size()) { - current -= state.world.province_size(); + // moving to itself or having no paths to go out implies deletion + if(target == -1 || target == p.graph_node_current) { + p.graph_node_current = -1; + p.ship_model = {}; + } else { + auto current = p.graph_node_current; + auto current_prov = dcon::province_id{ dcon::province_id::value_base_t(current) }; + if( + current < state.province_definitions.first_sea_province.index() + && target >= state.province_definitions.first_sea_province.index() + && !p.ship_model + ) { + auto local_state = state.world.province_get_state_membership(current_prov); + auto local_market = state.world.state_instance_get_market_from_local_market(local_state); + auto total = 0.f; + for(uint32_t ui = 2; ui < state.military_definitions.unit_base_definitions.size(); ++ui) { + dcon::unit_type_id uid = dcon::unit_type_id{ dcon::unit_type_id::value_base_t(ui) }; + auto count = state.world.market_get_owned_ships(local_market, uid); + total += count; } - - auto adj = state.world.get_province_adjacency_by_province_pair( - dcon::province_id{ dcon::province_id::value_base_t(target) }, - dcon::province_id{ dcon::province_id::value_base_t(current) } - ); - if(adj && railroad_counts[adj.index()] > 0) { - p.adj_index = adj.index(); - p.adj_count = 0; - p.adj_direction = 1; - if(state.world.province_adjacency_get_connected_provinces(adj, 0).index() != target) { - p.adj_count = railroad_counts[adj.index()] - 1; - p.adj_direction = -1; + auto dice = fmod(sin(i * time_counter * 791541.5347643) + 1.f, 1.f) * total; + auto accumulated_ship = 0.f; + for(uint32_t ui = 2; ui < state.military_definitions.unit_base_definitions.size(); ++ui) { + dcon::unit_type_id uid = dcon::unit_type_id{ dcon::unit_type_id::value_base_t(ui) }; + auto count = state.world.market_get_owned_ships(local_market, uid); + accumulated_ship += count; + if (accumulated_ship > dice) { + p.ship_model = uid; + break; } } } + p.graph_node_next = target; + p.target_ = put_in_local(flow_data.node_position[target], p.position_, (float)size_x); + + auto adj = state.world.get_province_adjacency_by_province_pair( + dcon::province_id{ dcon::province_id::value_base_t(target) }, + dcon::province_id{ dcon::province_id::value_base_t(current) } + ); + if(adj && railroad_counts[adj.index()] > 0) { + p.adj_index = adj.index(); + p.adj_count = 0; + p.adj_direction = 1; + if(state.world.province_adjacency_get_connected_provinces(adj, 0).index() != target) { + p.adj_count = railroad_counts[adj.index()] - 1; + p.adj_direction = -1; + } + } } + } - // spawn "new" particles - // don't spawn them too often - if(!spawned_something && p.trade_graph_node_current == -1) { - spawned_something = true; + // spawn "new" particles + // don't spawn them too often + if(attempts_to_spawn > 0 && !spawned_something && p.graph_node_current == -1) { + auto p_types = state.world.commodity_size(); + if(flow_data.source == flow_map::administration) { + p_types = 1; + } + + auto random = fmod(sin(((float)i + time_counter) * 92637.1323076) + 1.f, 1.f) * state.world.commodity_size(); + auto next_edge_layer_dice = (float) int(random) / (float)state.world.commodity_size(); + float accumulated_edge_dice = 0.f; + flow_data.flow_particles_content[i] = -1; + + + if(p_types == 1) { + flow_data.flow_particles_content[i] = 0; + } else { + for(size_t candidate = 0; candidate < p_types; candidate++) { + accumulated_edge_dice += flow_data.edge_layer_probability[candidate]; + if(next_edge_layer_dice < accumulated_edge_dice) { + flow_data.flow_particles_content[i] = (int32_t)candidate; + break; + } + } + } + + if(flow_data.flow_particles_content[i] != -1) { + attempts_to_spawn--; - auto random = fmod(sin(time_counter * 92637.1323076) + 1.f, 1.f); + random = random - (float)(int(random)); int target = -1; float accumulated = 0.f; - for(auto const& [candidate, probability] : particle_creation_probability) { - accumulated += probability; + + for(size_t candidate = 0; candidate < flow_data.node_probability_create.size(); candidate++) { + accumulated += flow_data.node_probability_create[candidate][flow_data.flow_particles_content[i]]; if(random < accumulated) { - target = candidate; + target = (int)candidate; break; } } if(target != -1) { - p.trade_graph_node_current = target; - p.position_ = trade_node_position[target]; - p.target_ = trade_node_position[target]; - p.trade_graph_node_next = -1; - p.trade_graph_node_prev = -1; + p.graph_node_current = target; + p.position_ = flow_data.node_position[target]; + p.target_ = flow_data.node_position[target]; + p.graph_node_next = -1; + p.graph_node_prev = -1; + //spawned_something = true; } } } } + // draw { - glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + auto texture_handle = ogl::get_texture_handle(state, gfx_def.primary_texture_handle, gfx_def.is_partially_transparent()); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture_handle); - glUniform1i(shader_uniforms[shader_map_sprite][uniform_texture_sampler], 0); - glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_start], (float)frame / gfx_def.number_of_frames, 0.f); glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_size], 1.f / gfx_def.number_of_frames, 1.f); glBindVertexArray(vao_array[vo_square]); glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_square]); - for(size_t i = 0; i < trade_particles_positions.size(); i++) { - auto& p = trade_particles_positions[i]; - if(p.trade_graph_node_current != -1) { - for(int vagon = (int)p.vagon_positions.size() - 1; vagon > 0; vagon--) { - glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], trade_particles_positions[i].vagon_positions[vagon].x / float(size_x), trade_particles_positions[i].vagon_positions[vagon].y / float(size_y)); + for(size_t i = 0; i < flow_data.flow_particles_positions.size(); i++) { + auto& p = flow_data.flow_particles_positions[i]; + auto edge = flow_data.flow_particles_content[i]; + auto frame = state.world.commodity_get_icon(dcon::commodity_id{ (dcon::commodity_id::value_base_t)edge }); + if(flow_data.source == flow_map::administration) { + frame = state.world.pop_type_get_sprite(state.culture_definitions.bureaucrat) - 1; + } + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_start], (float)frame / gfx_def.number_of_frames, 0.f); + + if(p.graph_node_current != -1) { + for(int wagon = (int)p.wagon_positions.size() - 3; wagon > 0; wagon -= 3) { + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], p.wagon_positions[wagon].x / float(size_x), p.wagon_positions[wagon].y / float(size_y)); glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_scale], 1.f / float(size_x), 1.f / float(size_y)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -1738,8 +1810,8 @@ void display_data::render( glBindVertexArray(vao_array[vo_square]); glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_square]); - for(size_t i = 0; i < trade_particles_positions.size(); i++) { - auto& p = trade_particles_positions[i]; + for(size_t i = 0; i < flow_data.flow_particles_positions.size(); i++) { + auto& p = flow_data.flow_particles_positions[i]; dcon::province_adjacency_id adj{ dcon::province_adjacency_id::value_base_t(p.adj_index) }; auto p1 = state.world.province_adjacency_get_connected_provinces(adj, 0); @@ -1751,25 +1823,56 @@ void display_data::render( continue; } - if(p.trade_graph_node_current != -1) { - glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], trade_particles_positions[i].position_.x / float(size_x), trade_particles_positions[i].position_.y / float(size_y)); + if(p.graph_node_current != -1) { + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], p.position_.x / float(size_x), p.position_.y / float(size_y)); glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_scale], 1.f / float(size_x), 1.f / float(size_y)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } } + + glBindVertexArray(vao_array[vo_square]); + glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_square]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textures[texture_ship]); - glUniform1i(shader_uniforms[shader_map_sprite][uniform_texture_sampler], 0); glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_start], 0.f, 0.f); glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_size], 1.f, 1.f); - glBindVertexArray(vao_array[vo_square]); - glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_square]); + for(size_t i = 0; i < flow_data.flow_particles_positions.size(); i++) { + auto& p = flow_data.flow_particles_positions[i]; + if(p.ship_model) { + continue; + } + dcon::province_adjacency_id adj{ dcon::province_adjacency_id::value_base_t(p.adj_index) }; + auto p1 = state.world.province_adjacency_get_connected_provinces(adj, 0); + auto p2 = state.world.province_adjacency_get_connected_provinces(adj, 1); + auto p1_is_sea = p1.index() >= state.province_definitions.first_sea_province.index() && p1.index() < (int)state.world.province_size(); + auto p2_is_sea = p2.index() >= state.province_definitions.first_sea_province.index() && p2.index() < (int)state.world.province_size(); + + if(!(p1_is_sea || p2_is_sea)) { + continue; + } + + if(p.graph_node_current != -1) { + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], p.position_.x / float(size_x), p.position_.y / float(size_y)); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_scale], 1.f / float(size_x), 1.f / float(size_y)); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + + auto texture_handle_ships = ogl::get_texture_handle(state, gfx_def_ships.primary_texture_handle, gfx_def_ships.is_partially_transparent()); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_handle_ships); + glUniform1i(shader_uniforms[shader_map_sprite][uniform_texture_sampler], 0); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_size], 1.f / gfx_def_ships.number_of_frames, 1.f); + + for(size_t i = 0; i < flow_data.flow_particles_positions.size(); i++) { + auto& p = flow_data.flow_particles_positions[i]; + if(!p.ship_model) { + continue; + } - for(size_t i = 0; i < trade_particles_positions.size(); i++) { - auto& p = trade_particles_positions[i]; dcon::province_adjacency_id adj{ dcon::province_adjacency_id::value_base_t(p.adj_index) }; auto p1 = state.world.province_adjacency_get_connected_provinces(adj, 0); @@ -1781,12 +1884,16 @@ void display_data::render( continue; } - if(p.trade_graph_node_current != -1) { - glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], trade_particles_positions[i].position_.x / float(size_x), trade_particles_positions[i].position_.y / float(size_y)); + auto frame = state.military_definitions.unit_base_definitions[p.ship_model].icon - 1; + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_start], (float)frame / gfx_def_ships.number_of_frames, 0.f); + + if(p.graph_node_current != -1) { + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], p.position_.x / float(size_x), p.position_.y / float(size_y)); glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_scale], 1.f / float(size_x), 1.f / float(size_y)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } } + } } } @@ -2533,6 +2640,12 @@ void add_tl_segment_buffer( end /= glm::vec2(size_x, size_y); auto d = start - end; distance += glm::length(d); + + assert(start.y >= 0.f && start.y < 1.f); + assert(end.y >= 0.f && end.y < 1.f); + assert(std::isfinite(width_end) && width_end >= 0.f); + assert(std::isfinite(next_normal_dir.x) && std::isfinite(next_normal_dir.y)); + buffer.emplace_back(textured_line_with_width_vertex{ end, +next_normal_dir, 0.0f, distance, width_end });//C buffer.emplace_back(textured_line_with_width_vertex{ end, -next_normal_dir, 1.0f, distance, width_end });//D } @@ -2655,9 +2768,18 @@ void add_bezier_to_buffer_variable_width( auto point = bpoint(t_current); auto point_next = bpoint(t_next); + if(point_prev == point && point == point_next) { + t_prev = t_current; + continue; + } + auto approximate_normal = glm::normalize(point - point_prev) + glm::normalize(point - point_next); - auto local_tangent = glm::normalize(point - point_prev); - if(glm::length(approximate_normal) < 0.00001f) { + auto local_tangent = glm::normalize(point_next - point_prev); + if( + glm::length(point - point_prev) < 0.00001f + || glm::length(point - point_next) < 0.00001f + || glm::length(approximate_normal) < 0.00001f + ) { approximate_normal = glm::normalize(glm::vec2(-local_tangent.y, local_tangent.x)); } else { approximate_normal = glm::normalize(approximate_normal); @@ -2665,6 +2787,7 @@ void add_bezier_to_buffer_variable_width( approximate_normal = -approximate_normal; } } + assert(std::isfinite(approximate_normal.x) && std::isfinite(approximate_normal.y)); if(!request_higher_density) { if(glm::dot(approximate_normal, last_normal) < 0.98f) { diff --git a/src/map/map.hpp b/src/map/map.hpp index df50d8c9e..ca9ece209 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -33,17 +33,6 @@ struct vertex { glm::vec2 texcoord_; }; -struct trade_particle { - glm::vec2 position_; - glm::vec2 target_; - int trade_graph_node_current; - int trade_graph_node_prev = -1; - int trade_graph_node_next; - int adj_index = -1; - int adj_count = 0; - int adj_direction = 1; - std::array vagon_positions {} ; -}; struct screen_vertex { screen_vertex(float x, float y) : position_(x, y){}; glm::vec2 position_; @@ -246,11 +235,6 @@ class display_data { std::vector trade_flow_vertices; std::vector trade_flow_arrow_starts; std::vector trade_flow_arrow_counts; - // trade particles - std::vector trade_particles_positions; - ankerl::unordered_dense::map> particle_next_node_probability; - ankerl::unordered_dense::map particle_creation_probability; - ankerl::unordered_dense::map trade_node_position; // std::vector unit_arrow_vertices; std::vector unit_arrow_starts; diff --git a/src/map/map_modes.cpp b/src/map/map_modes.cpp index a15bef3bf..0cf433596 100644 --- a/src/map/map_modes.cpp +++ b/src/map/map_modes.cpp @@ -872,6 +872,14 @@ void set_map_mode(sys::state& state, mode mode) { state.ui_state.map_rec_legend->set_visible(state, false); } + if(state.flow_map.source == flow_map::data_source::administration && mode != mode::admin) { + if(state.selected_trade_good) { + state.flow_map.request_update(flow_map::data_source::commodity); + } else { + state.flow_map.request_update(flow_map::data_source::none); + } + } + switch(mode) { case mode::state_select: prov_color = select_states_map_from(state); @@ -929,6 +937,7 @@ void set_map_mode(sys::state& state, mode mode) { break; case mode::admin: prov_color = admin_map_from(state); + state.flow_map.request_update(flow_map::data_source::administration); break; case mode::naval: prov_color = naval_map_from(state); diff --git a/src/map/map_state.cpp b/src/map/map_state.cpp index 069a6f1cb..c158445a8 100644 --- a/src/map/map_state.cpp +++ b/src/map/map_state.cpp @@ -116,948 +116,10 @@ glm::vec2 get_navy_location(sys::state& state, dcon::province_id prov_id) { return get_port_location(state, prov_id); } -glm::vec2 get_army_location(sys::state& state, dcon::province_id prov_id) { +glm::vec2 get_army_location(sys::state const& state, dcon::province_id prov_id) { return state.world.province_get_mid_point(prov_id); } -void register_trade_flow(display_data& map_data, int node1, int node2, float volume) { - assert(node1 >= 0); - assert(node2 >= 0); - if(map_data.particle_next_node_probability.contains(node1)) { - if(map_data.particle_next_node_probability[node1].contains(node2)) { - map_data.particle_next_node_probability[node1][node2] += volume; - } else { - map_data.particle_next_node_probability[node1][node2] = volume; - } - } else { - map_data.particle_next_node_probability[node1] = { }; - map_data.particle_next_node_probability[node1][node2] = volume; - } -} - -void update_trade_flow_arrows(sys::state& state, display_data& map_data) { - // idea: - // trade graph emits particles which move from the start of edge to the end of edge - // when particle reaches the node, it has a chance to dissappear - // depending on difference between trade inflow and outflow - // or it can be redirected to one of other nodes depending on the volume - - map_data.trade_particles_positions.clear(); - map_data.particle_next_node_probability.clear(); - map_data.particle_creation_probability.clear(); - - auto size_x = float(map_data.size_x); - auto size_y = float(map_data.size_y); - - map_data.trade_flow_vertices.clear(); - map_data.trade_flow_arrow_counts.clear(); - map_data.trade_flow_arrow_starts.clear(); - - auto cid = state.selected_trade_good; - - if(!cid) { - return; - } - - // we start with getting some stats on trade routes: - // we do not want to display too many of them - - auto total = 0.f; - auto count = 0.f; - - std::vector volume_sample; - float total_volume = 0.f; - - state.world.for_each_trade_route([&](dcon::trade_route_id trade_route) { - auto current_volume = state.world.trade_route_get_volume(trade_route, cid); - auto origin = state.world.trade_route_get_origin(trade_route); - auto target = state.world.trade_route_get_target(trade_route); - auto sat = state.world.market_get_actual_probability_to_buy(origin, cid); - auto absolute_volume = std::abs(sat * current_volume); - total_volume += absolute_volume; - volume_sample.push_back(absolute_volume); - }); - - if(total_volume == 0.f) { - return; - } - - std::sort(volume_sample.begin(), volume_sample.end()); - // we are interested only in the most significant routes - auto cutoff = std::max(0.01f, volume_sample[9 * volume_sample.size() / 10] * 0.5f); - - // start with construction of trade_graph - std::map> trade_graph; - - std::map trade_graph_toward_port; - /* - state.world.for_each_province([&](dcon::province_id province) { - auto local_in = economy::estimate_intermediate_consumption(state, cid, province) + economy::estimate_pops_consumption(state, cid, province); - auto local_out = economy::estimate_production(state, cid, province); - auto absolute_volume = std::abs(local_out - local_in); - if(absolute_volume < cutoff) { - return; - } - - auto sid = state.world.province_get_state_membership(province); - //auto mid = state.world.state_instance_get_market_from_local_market(sid); - auto target = state.world.state_instance_get_capital(sid).id; - - if(local_in > local_out) { - auto path = province::make_unowned_naval_path(state, province, target); - auto start = province; - for(int i = int(path.size()) - 1; i >= 0; i--) { - auto end = path[i]; - if(trade_graph.contains(start.index())) { - if(trade_graph[start.index()].contains(end.index())) { - trade_graph[start.index()][end.index()] += absolute_volume; - } else { - trade_graph[start.index()][end.index()] = absolute_volume; - } - } else { - trade_graph[start.index()] = std::map{ }; - trade_graph[start.index()][end.index()] = absolute_volume; - } - - auto start_index = start.index(); - if(start.index() < state.province_definitions.first_sea_province.index()) { - start_index += state.world.province_size(); - } - auto end_index = end.index(); - if(end.index() < state.province_definitions.first_sea_province.index()) { - end_index += state.world.province_size(); - } - register_trade_flow(map_data, start_index, end_index, absolute_volume); - - start = end; - } - } else { - auto path = province::make_unowned_naval_path(state, target, province); - auto start = target; - for(int i = int(path.size()) - 1; i >= 0; i--) { - auto end = path[i]; - if(trade_graph.contains(start.index())) { - if(trade_graph[start.index()].contains(end.index())) { - trade_graph[start.index()][end.index()] += absolute_volume; - } else { - trade_graph[start.index()][end.index()] = absolute_volume; - } - } else { - trade_graph[start.index()] = std::map{ }; - trade_graph[start.index()][end.index()] = absolute_volume; - } - - auto start_index = start.index(); - if(start.index() < state.province_definitions.first_sea_province.index()) { - start_index += state.world.province_size(); - } - auto end_index = end.index(); - if(end.index() < state.province_definitions.first_sea_province.index()) { - end_index += state.world.province_size(); - } - register_trade_flow(map_data, start_index, end_index, absolute_volume); - - start = end; - } - } - }); - */ - - state.world.for_each_trade_route([&](dcon::trade_route_id trade_route) { - auto current_volume = state.world.trade_route_get_volume(trade_route, cid); - auto origin = state.world.trade_route_get_origin(trade_route); - auto target = state.world.trade_route_get_target(trade_route); - auto s_origin = state.world.market_get_zone_from_local_market(origin); - auto s_target = state.world.market_get_zone_from_local_market(target); - auto p_origin = state.world.state_instance_get_capital(s_origin); - auto p_target = state.world.state_instance_get_capital(s_target); - auto sat = state.world.market_get_actual_probability_to_buy(origin, cid); - auto absolute_volume = std::abs(sat * current_volume); - - if(absolute_volume < cutoff) { - return; - } - - bool is_sea = state.world.trade_route_get_is_sea_route(trade_route); - if(is_sea) { - auto coast_origin = province::state_get_coastal_capital(state, s_origin); - auto coast_target = province::state_get_coastal_capital(state, s_target); - - auto path = province::make_sea_trade_route_path(state, coast_origin, coast_target); - auto start = coast_origin; - for(int i = int(path.size()) - 1; i >= 0; i--) { - auto end = path[i]; - if(trade_graph.contains(start.index())) { - if(trade_graph[start.index()].contains(end.index())) { - trade_graph[start.index()][end.index()] += absolute_volume; - } else { - trade_graph[start.index()][end.index()] = absolute_volume; - } - } else { - trade_graph[start.index()] = std::map{ }; - trade_graph[start.index()][end.index()] = absolute_volume; - } - - auto start_index = start.index(); - if(start.index() < state.province_definitions.first_sea_province.index()) { - start_index += state.world.province_size(); - } - auto end_index = end.index(); - if(end.index() < state.province_definitions.first_sea_province.index()) { - end_index += state.world.province_size(); - } - register_trade_flow(map_data, start_index, end_index, absolute_volume); - - start = end; - } - } else { - auto path = province::make_land_trade_path(state, p_origin, p_target); - auto start = p_origin.id; - for(int i = int(path.size()) - 1; i >= 0; i--) { - auto end = path[i]; - if(trade_graph.contains(start.index())) { - if(trade_graph[start.index()].contains(end.index())) { - trade_graph[start.index()][end.index()] += absolute_volume; - } else { - trade_graph[start.index()][end.index()] = absolute_volume; - } - } else { - trade_graph[start.index()] = { }; - trade_graph[start.index()][end.index()] = absolute_volume; - } - - register_trade_flow(map_data, start.index(), end.index(), absolute_volume); - - start = end; - } - } - }); - - state.world.for_each_market([&](dcon::market_id mid) { - auto trade_balance_sea = 0.f; - auto trade_balance_land = 0.f; - - auto total_in = 0.f; - auto total_out = 0.f; - - state.world.market_for_each_trade_route_as_origin(mid, [&](auto route) { - auto current_volume = state.world.trade_route_get_volume(route, cid); - auto origin = state.world.trade_route_get_origin(route); - auto target = state.world.trade_route_get_target(route); - auto sat = state.world.market_get_actual_probability_to_buy(origin, cid); - - bool is_sea = state.world.trade_route_get_is_sea_route(route); - - if(is_sea) { - trade_balance_sea += current_volume * sat; - } else { - trade_balance_land += current_volume * sat; - } - - if(current_volume > 0) { - total_out += current_volume * sat; - } else { - total_in -= current_volume * sat; - } - }); - - - - // if total in > total out - // we assume that commodity has a chance to be consumed - // it will be represented by a loop edge - // so we can assume that total out is actually total in - - auto disappear_weight = 0.f; - if(total_in > total_out) { - total_out = total_in; - disappear_weight = total_in - total_out; - } - - - - auto sid = state.world.market_get_zone_from_local_market(mid); - auto port = province::state_get_coastal_capital(state, sid); - auto stockpile_location = state.world.state_instance_get_capital(sid); - auto absolute_volume = std::abs(trade_balance_sea); - - - // set trade graph edges - - auto stockpile_index = stockpile_location.id.index(); - register_trade_flow(map_data, stockpile_index, stockpile_index, disappear_weight); - - - if(total_out > total_in) { - map_data.particle_creation_probability[stockpile_index] = total_out - total_in; - } - - if(trade_balance_sea > cutoff) { - auto path = province::make_land_trade_path(state, stockpile_location, port); - auto start = stockpile_location.id; - for(int i = int(path.size()) - 1; i >= 0; i--) { - auto end = path[i]; - if(trade_graph.contains(start.index())) { - if(trade_graph[start.index()].contains(end.index())) { - trade_graph[start.index()][end.index()] += absolute_volume; - } else { - trade_graph[start.index()][end.index()] = absolute_volume; - } - } else { - trade_graph[start.index()] = { }; - trade_graph[start.index()][end.index()] = absolute_volume; - } - - register_trade_flow(map_data, start.index(), end.index(), absolute_volume); - - start = end; - } - trade_graph_toward_port[port.index()] = absolute_volume; - - auto port_index = port.index() + state.world.province_size(); - auto city_index = port.index(); - register_trade_flow(map_data, city_index, port_index, absolute_volume); - } else if(trade_balance_sea < -cutoff) { - auto path = province::make_land_trade_path(state, port, stockpile_location); - auto start = port; - for(int i = int(path.size()) - 1; i >= 0; i--) { - auto end = path[i]; - - if(trade_graph.contains(start.index())) { - if(trade_graph[start.index()].contains(end.index())) { - trade_graph[start.index()][end.index()] += absolute_volume; - } else { - trade_graph[start.index()][end.index()] = absolute_volume; - } - } else { - trade_graph[start.index()] = { }; - trade_graph[start.index()][end.index()] = absolute_volume; - } - - register_trade_flow(map_data, start.index(), end.index(), absolute_volume); - - start = end; - } - - trade_graph_toward_port[port.index()] = -absolute_volume; - - auto port_index = port.index() + state.world.province_size(); - auto city_index = port.index(); - register_trade_flow(map_data, port_index, city_index, absolute_volume); - } - }); - - // generate some particles: - - for(int i = 0; i < std::min(std::max(300, (int)total_volume), 5000); i++) { - trade_particle data{ - { }, - { }, - -1, - -1, - -1 - }; - map_data.trade_particles_positions.push_back(data); - } - - // normalisation: - - { - float total_score = 0.f; - for(auto const& [candidate, probability] : map_data.particle_creation_probability) { - total_score += probability; - } - - if(total_score > 0.f) { - for(auto const& [candidate, probability] : map_data.particle_creation_probability) { - map_data.particle_creation_probability[candidate] /= total_score; - } - } - } - - state.world.for_each_province([&](dcon::province_id origin) { - // normal prov - - auto base_index = origin.index(); - if(map_data.particle_next_node_probability.contains(base_index)) { - float total_volume_out = 0.f; - for(auto const& [target_index, volume] : map_data.particle_next_node_probability[base_index]) { - total_volume_out += volume; - } - - for(auto const& [target_index, volume] : map_data.particle_next_node_probability[base_index]) { - map_data.particle_next_node_probability[base_index][target_index] = volume / total_volume_out; - } - } - - map_data.trade_node_position[base_index] = get_army_location(state, origin); - - // port prov - - base_index += state.world.province_size(); - if(map_data.particle_next_node_probability.contains(base_index)) { - float total_volume_out = 0.f; - for(auto const& [target_index, volume] : map_data.particle_next_node_probability[base_index]) { - total_volume_out += volume; - } - - if(total_volume_out > 0.f) - for(auto const& [target_index, volume] : map_data.particle_next_node_probability[base_index]) { - map_data.particle_next_node_probability[base_index][target_index] = volume / total_volume_out; - } - } - - if(state.world.province_get_port_to(origin)) { - map_data.trade_node_position[base_index] = get_port_location(state, origin) ; - } else { - map_data.trade_node_position[base_index] = get_army_location(state, origin); - } - }); - - - auto is_sea = [&](dcon::province_id x) { - return x.value >= state.province_definitions.first_sea_province.value; - }; - - // build arbitrary distance field - - // choose "previous" node for every node - // propagate distance backward along previous node - // propagate distance forward with usual nodes - std::map visited; - state.world.for_each_province([&](dcon::province_id origin) { visited[origin.index()] = false; }); - - float the_most_fat_route = 0.f; - - std::map> trade_graph_incoming; - state.world.for_each_province([&](dcon::province_id origin) { - trade_graph_incoming[origin.index()] = { }; - }); - // todo: use the most important node as previous by storing "volume" and updating previous only when volume gets larger - std::map previous; - state.world.for_each_province([&](dcon::province_id origin) { - if(trade_graph.contains(origin.index())) { - for(auto& [key, value] : trade_graph[origin.index()]) { - previous[key] = origin.index(); - trade_graph_incoming[key][origin.index()] = value; - the_most_fat_route = std::max(the_most_fat_route, value); - } - } - }); - - std::map> trade_graph_max_incoming; - state.world.for_each_province([&](dcon::province_id origin) { - trade_graph_max_incoming[origin.index()] = { }; - }); - - state.world.for_each_province([&](dcon::province_id origin) { - if(trade_graph.contains(origin.index()) || previous.contains(origin.index())) { - - auto total_outgoing = 0.f; - auto total_incoming = 0.f; - - for(auto const& [index, volume] : trade_graph[origin.index()]) { - total_outgoing += volume; - } - - for(auto const& [index, volume] : trade_graph_incoming[origin.index()]) { - total_incoming += volume; - } - - auto left_outgoing = total_outgoing; - auto left_incoming = total_incoming; - - for(auto const& [target_index, target_volume] : trade_graph[origin.index()]) { - for(auto const& [source_index, source_volume] : trade_graph_incoming[origin.index()]) { - auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; - auto source = dcon::province_id{ dcon::province_id::value_base_t(source_index) }; - - if(source == target) { - continue; - } - - // if land->[port]->sea - abort - // if sea->[port]->land - abort - if(is_sea(source) && !is_sea(origin) && !is_sea(target))continue; - if(!is_sea(source) && !is_sea(origin) && is_sea(target))continue; - - auto volume_start = - source_volume; - auto volume_end = - volume_start - * map_data.particle_next_node_probability[origin.index()][target_index]; - - left_incoming -= volume_end; - left_outgoing -= volume_end; - - auto found = trade_graph_max_incoming[target_index].find(origin.index()); - if(found == trade_graph_max_incoming[target_index].end()) - trade_graph_max_incoming[target_index][origin.index()] = volume_end; - else if(trade_graph_max_incoming[target_index][origin.index()] < volume_end) - trade_graph_max_incoming[target_index][origin.index()] = volume_end; - } - } - - if(left_outgoing > 0.001f) { - for(auto const& [target_index, target_volume] : trade_graph[origin.index()]) { - auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; - auto volume = target_volume * left_outgoing / total_outgoing; - - auto found = trade_graph_max_incoming[target_index].find(origin.index()); - if(found == trade_graph_max_incoming[target_index].end()) - trade_graph_max_incoming[target_index][origin.index()] = volume; - else if(trade_graph_max_incoming[target_index][origin.index()] < volume) - trade_graph_max_incoming[target_index][origin.index()] = volume; - } - } - } - }); - - - std::map distance_field; - std::vector to_visit; - size_t current_index_to_visit = 0; - state.world.for_each_province([&](dcon::province_id origin) { - if(trade_graph.contains(origin.index()) && !visited[origin.index()]) { - to_visit.clear(); - to_visit.push_back(origin.index()); - current_index_to_visit = 0; - distance_field[origin.index()] = 0.f; - - while(current_index_to_visit < to_visit.size()) { - auto current = to_visit[current_index_to_visit]; - auto prev_it = previous.find(current); - if(prev_it != previous.end() && !visited[prev_it->second]) { - auto edge_volume = trade_graph[prev_it->second][current]; - auto width = std::min(std::sqrt(std::abs(edge_volume)) / cutoff, 5.f) * 1000.f; - distance_field[prev_it->second] = distance_field[current] - province::direct_distance( - state, - dcon::province_id{ (dcon::province_id::value_base_t)(current) }, - dcon::province_id{ (dcon::province_id::value_base_t)(prev_it->second) } - ) / width; - to_visit.push_back(prev_it->second); - visited[prev_it->second] = true; - } - - for(auto const& [target_index, volume] : trade_graph[origin.index()]) { - if(!visited[target_index]) { - auto edge_volume = trade_graph[current][target_index]; - auto width = std::min(std::sqrt(std::abs(edge_volume)) / cutoff, 5.f) * 1000.f; - distance_field[target_index] = distance_field[current] + province::direct_distance( - state, - dcon::province_id{ (dcon::province_id::value_base_t)(current) }, - dcon::province_id{ (dcon::province_id::value_base_t)(target_index) } - ) / width; - to_visit.push_back(target_index); - visited[target_index] = true; - } - } - - current_index_to_visit++; - } - }; - }); - - // now we are building vertices - - auto volume_to_width = [&](float volume) { - return std::abs(volume) / std::max(0.05f, the_most_fat_route) * 40000.f; - }; - - //for(auto const& [coastal_province_index, volume] : trade_graph_toward_port) { - // auto coastal_province = dcon::province_id{ dcon::province_id::value_base_t(coastal_province_index) }; - // glm::vec2 current_pos = get_army_location(state, coastal_province); - // glm::vec2 next_pos = put_in_local(get_port_location(state, coastal_province), current_pos, size_x); - // auto port_direction = get_port_direction(state, coastal_province); - // if(volume < 0.f) { - // current_pos = put_in_local(get_port_location(state, coastal_province), current_pos, size_x); - // next_pos = put_in_local(get_army_location(state, coastal_province), current_pos, size_x); - // port_direction = -port_direction; - // } - // glm::vec2 prev_tangent = glm::normalize(next_pos - current_pos); - // auto start_normal = glm::vec2(-prev_tangent.y, prev_tangent.x); - // auto norm_pos = current_pos / glm::vec2(size_x, size_y); - // auto norm_next_pos = next_pos / glm::vec2(size_x, size_y); - // auto old_size = map_data.trade_flow_vertices.size(); - // map_data.trade_flow_arrow_starts.push_back(GLint(old_size)); - // auto width = volume_to_width(volume); - // map_data.trade_flow_vertices.emplace_back(map::textured_line_with_width_vertex{ - // norm_pos, - // +start_normal, - // 0.f, - // 0.0f, - // width - // }); - // map_data.trade_flow_vertices.emplace_back(map::textured_line_with_width_vertex{ - // norm_pos, - // -start_normal, - // 1.f, - // 0.0f, - // width - // }); - // // assume that province is a square - // auto distance = - // std::sqrt(float(state.map_state.map_data.province_area[province::to_map_id(coastal_province)])) - // / 20000.f; - // map_data.trade_flow_vertices.emplace_back(map::textured_line_with_width_vertex{ - // norm_next_pos, - // +start_normal, - // 0.f, - // distance, - // width - // }); - // map_data.trade_flow_vertices.emplace_back(map::textured_line_with_width_vertex{ - // norm_next_pos, - // -start_normal, - // 1.f, - // distance, - // width - // }); - // map_data.trade_flow_arrow_counts.push_back( - // GLsizei(map_data.trade_flow_vertices.size() - old_size) - // ); - //} - - std::map vertices_built; - state.world.for_each_province([&](dcon::province_id origin) { vertices_built[origin.index()] = false; }); - - auto build_bezier = [&](glm::vec2 start, glm::vec2 start_tangent, float start_width, glm::vec2 end, glm::vec2 end_tangent, float end_width, float& distance) { - auto old_size = map_data.trade_flow_vertices.size(); - map_data.trade_flow_arrow_starts.push_back(GLint(old_size)); - auto start_normal = glm::vec2(-start_tangent.y, start_tangent.x); - auto norm_pos = start / glm::vec2(size_x, size_y); - map_data.trade_flow_vertices.emplace_back(map::textured_line_with_width_vertex{ - norm_pos, - start_normal, - 0.f, - distance, - start_width - }); - map_data.trade_flow_vertices.emplace_back(map::textured_line_with_width_vertex{ - norm_pos, - -start_normal, - 1.f, - distance, - start_width - }); - add_bezier_to_buffer_variable_width( - map_data.trade_flow_vertices, - start, - end, - start_tangent, - end_tangent, - 1.0f, - false, - size_x, - size_y, - 40, - distance, - start_width, - end_width, - end_width - ); - map_data.trade_flow_arrow_counts.push_back( - GLsizei(map_data.trade_flow_vertices.size() - old_size) - ); - }; - - state.world.for_each_province([&](dcon::province_id origin) { - - - if(trade_graph.contains(origin.index()) || previous.contains(origin.index())) { - - auto total_outgoing = 0.f; - auto total_incoming = 0.f; - - for(auto const& [index, volume] : trade_graph[origin.index()]) { - total_outgoing += volume; - } - - for(auto const& [index, volume] : trade_graph_incoming[origin.index()]) { - total_incoming += volume; - } - - auto left_outgoing = total_outgoing; - auto left_incoming = total_incoming; - - //auto max_incoming = 0.f; - //for(auto const& [target_index, target_volume] : trade_graph[origin.index()]) { - // for(auto const& [source_index, source_volume] : trade_graph_incoming[origin.index()]) { - // auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; - // auto source = dcon::province_id{ dcon::province_id::value_base_t(source_index) }; - // } - //} - - - - for(auto const& [target_index, target_volume] : trade_graph[origin.index()]) { - for(auto const& [source_index, source_volume] : trade_graph_incoming[origin.index()]) { - glm::vec2 current_pos = get_army_location(state, origin); - - auto volume_start = - source_volume; - auto volume_end = - volume_start - * map_data.particle_next_node_probability[origin.index()][target_index]; - - auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; - glm::vec2 next_pos = put_in_local(get_army_location(state, target), current_pos, size_x); - - auto source = dcon::province_id{ dcon::province_id::value_base_t(source_index) }; - glm::vec2 prev_pos = put_in_local(get_army_location(state, source), current_pos, size_x); - - if(source == target) { - // model of probabilistic movement is not perfect, so we have to ignore something when we attempt to move in a loop - left_incoming -= volume_end; - left_outgoing -= volume_end; - continue; - } - - auto adj = state.world.get_province_adjacency_by_province_pair(source, origin); - auto adj2 = state.world.get_province_adjacency_by_province_pair(origin, target); - bool not_lake = - ( - ( - state.world.province_adjacency_get_type(adj2) - | state.world.province_adjacency_get_type(adj) - ) - & province::border::impassible_bit - ) == 0; - - - // by default: connect centers of the segments between midpoints of provinces and use their tangents as start and end tangents - - glm::vec2 tangent_start = glm::normalize(current_pos - prev_pos); - glm::vec2 tangent_end = glm::normalize(next_pos - current_pos); - glm::vec2 start = (current_pos + prev_pos) / 2.f; - glm::vec2 end = (current_pos + next_pos) / 2.f; - - // sea -> [port->land->port] -> sea - if( - not_lake - && - is_sea(target) - && - !is_sea(origin) - && - is_sea(source) - ) { - // if we are entering a port from sea and then go back to sea, use port location instead of actual center - current_pos = put_in_local(get_port_location(state, origin), current_pos, size_x); - - // adjust tangents and start/end positions - tangent_start = glm::normalize(current_pos - prev_pos); - tangent_end = glm::normalize(next_pos - current_pos); - start = (current_pos + prev_pos) / 2.f; - end = (current_pos + next_pos) / 2.f; - } else if (not_lake) { - // ??? -> sea -> [port->land] - if( - is_sea(origin) - && - !is_sea(target) - ) { - // if we are currently at sea, but next position is a port, use port location as the end of the path - end = put_in_local(get_port_location(state, target), current_pos, size_x); - tangent_end = -get_port_direction(state, target); - } - // sea -> [port->land] -> ??? - if( - !is_sea(origin) - && - is_sea(source) - ) { - // currently we are at center of the province and we want to connect start with a path which was terminated at port - start = put_in_local(get_port_location(state, origin), current_pos, size_x); - tangent_start = -get_port_direction(state, origin); - } - - // ??? -> [land->port] -> sea - if( - !is_sea(origin) - && - is_sea(target) - ) { - // currently we are at center of the province and we want to connect end with a path which starts at port - end = put_in_local(get_port_location(state, origin), current_pos, size_x); - tangent_end = get_port_direction(state, origin); - } - // [land->port] -> sea -> ??? - if( - !is_sea(source) - && - is_sea(origin) - ) { - // if we are currently at sea, but previous position is a port, use port location as the start of the path - start = put_in_local(get_port_location(state, source), current_pos, size_x); - tangent_start = get_port_direction(state, source); - } - } - - left_incoming -= volume_end; - left_outgoing -= volume_end; - - if(volume_end == 0.f) { - continue; - } - - auto start_width = volume_to_width(trade_graph_max_incoming[origin.index()][source_index]); - if(start_width < 50.f) { - continue; - } - if(volume_to_width(volume_end) < 50.f) { - continue; - } - - // finally - float distance = distance_field[source_index]; - build_bezier( - start, tangent_start, start_width, end, tangent_end, volume_to_width(volume_end), distance - ); - } - } - - - - - if(left_incoming > 0.001f) { - // here we treat the case when some volume in was not matched with volume out - // in this case we assume that the remaining volume is "consumed" in the middle of the province - for(auto const& [source_index, source_volume] : trade_graph_incoming[origin.index()]) { - auto distance = distance_field[source_index]; - auto source = dcon::province_id{ dcon::province_id::value_base_t(source_index) }; - - glm::vec2 current_pos = get_army_location(state, origin); - glm::vec2 prev_pos = put_in_local(get_army_location(state, source), current_pos, size_x); - - glm::vec2 tangent_start = glm::normalize(current_pos - prev_pos); - glm::vec2 tangent_end = tangent_start; - glm::vec2 start = (current_pos + prev_pos) / 2.f; - glm::vec2 end = current_pos; - - //source -> origin - - auto adj = state.world.get_province_adjacency_by_province_pair(source, origin); - bool not_lake = (state.world.province_adjacency_get_type(adj) & province::border::impassible_bit) == 0; - - /* - if(!not_lake) { - continue; - } - */ - - //[land->port] -> sea - if( - not_lake - && - !is_sea(source) - && - is_sea(origin) - ) { - // just start from the (port location, port direction) and stop at (origin midpoint, direction from port to origin) - start = put_in_local(get_port_location(state, source), current_pos, size_x); - tangent_start = get_port_direction(state, source); - } - - //sea -> [port->land] - if( - not_lake - && - is_sea(source) - && - !is_sea(origin) - ) { - start = put_in_local(get_port_location(state, origin), current_pos, size_x); - tangent_start = -get_port_direction(state, origin); - } - - auto volume_start = source_volume * left_incoming / total_incoming; - auto volume_end = volume_start; - auto width_start = volume_to_width(volume_start); - auto width_end = volume_to_width(volume_end); - if(width_start < 50.f || width_end < 50.f) { - continue; - } - - build_bezier( - start, tangent_start, width_start, end, tangent_end, width_end, distance - ); - } - } - - if(left_outgoing > 0.001f) { - for(auto const& [target_index, target_volume] : trade_graph[origin.index()]) { - auto distance = distance_field[origin.index()]; - auto target = dcon::province_id{ dcon::province_id::value_base_t(target_index) }; - - glm::vec2 current_pos = get_army_location(state, origin); - glm::vec2 next_pos = put_in_local(get_army_location(state, target), current_pos, size_x); - glm::vec2 tangent_start = glm::normalize(next_pos - current_pos); - glm::vec2 tangent_end = tangent_start; - - //origin -> target - - auto adj = state.world.get_province_adjacency_by_province_pair(origin, target); - bool not_lake = (state.world.province_adjacency_get_type(adj) & province::border::impassible_bit) == 0; - - /* - if(!not_lake) { - continue; - } - */ - - //sea -> [port->land] - if( - not_lake - && - !is_sea(target) - && - is_sea(origin) - ) { - next_pos = put_in_local(get_port_location(state, target), current_pos, size_x); - tangent_end = -get_port_direction(state, target); - } - - //[land->port] -> sea - if( - not_lake - && - is_sea(target) - && - !is_sea(origin) - ) { - next_pos = put_in_local(get_port_location(state, origin), current_pos, size_x); - tangent_end = get_port_direction(state, origin); - } - - auto volume_start = target_volume * left_outgoing / total_outgoing; - auto volume_end = volume_start; - auto width_start = volume_to_width(volume_start); - auto width_end = volume_to_width(volume_end); - if(width_start < 50.f || width_end < 50.f) { - continue; - } - - build_bezier( - current_pos, tangent_start, width_start, next_pos, tangent_end, width_end, distance - ); - } - } - } - }); - - if(!map_data.trade_flow_vertices.empty()) { - glBindBuffer(GL_ARRAY_BUFFER, map_data.vbo_array[map_data.vo_trade_flow]); - glBufferData( - GL_ARRAY_BUFFER, - sizeof(textured_line_with_width_vertex) - * map_data.trade_flow_vertices.size(), - map_data.trade_flow_vertices.data(), - GL_STATIC_DRAW - ); - } -} - void clear_drawing(sys::state const& state, display_data& map_data) { map_data.map_drawing_mutex.lock(); map_data.arbitrary_map_triangles_counts.clear(); @@ -2744,10 +1806,8 @@ void map_state::update(sys::state& state) { } state.map_state.map_data.map_drawing_mutex.unlock(); - if(state.selected_trade_good && state.update_trade_flow.load(std::memory_order::acquire)) { - update_trade_flow_arrows(state, map_data); - state.update_trade_flow.store(false, std::memory_order_release); - } + flow_map::update(state); + update_unit_arrows(state, map_data); // Update railroads, only if railroads are being built and we have 'em enabled @@ -2921,8 +1981,7 @@ void map_state::update(sys::state& state) { if(unhandled_province_selection) { map_mode::update_map_mode(state); - state.update_trade_flow.store(true, std::memory_order::release); - + state.flow_map.request_update(state.flow_map.source); map_data.update_highlight(state); unhandled_province_selection = false; } diff --git a/src/nations/nations.cpp b/src/nations/nations.cpp index 6f40f89b8..5e351b643 100644 --- a/src/nations/nations.cpp +++ b/src/nations/nations.cpp @@ -814,7 +814,7 @@ void update_national_administrative_efficiency(sys::state& state) { }); } -float admin_cost_of_province(sys::state& state, dcon::province_id pid) { +float admin_cost_of_province(sys::state const& state, dcon::province_id pid) { auto population = state.world.province_get_demographics(pid, demographics::total); auto area = state.map_state.map_data.province_area_km2[province::to_map_id(pid)]; auto is_coastal = state.world.province_get_is_coast(pid); @@ -846,12 +846,12 @@ ve::fp_vector ve_admin_cost_of_province(sys::state& state, T pid) { return (population * population_concentration + area * 100.f) * (1.f / (1.01f - current_control) - 1.f) + 100.f; } -float desire_score_province(sys::state& state, dcon::province_id pid) { +float desire_score_province(sys::state const& state, dcon::province_id pid) { auto base_score = state.world.province_get_demographics(pid, demographics::total) / admin_cost_of_province(state, pid); return std::min(2.f, base_score * base_score / 4.f); } -float control_shift_weight_mult(sys::state& state, dcon::province_adjacency_id adj) { +float control_shift_weight_mult(sys::state const& state, dcon::province_adjacency_id adj) { auto A = state.world.province_adjacency_get_connected_provinces(adj, 0); auto B = state.world.province_adjacency_get_connected_provinces(adj, 1); auto flag = state.world.province_adjacency_get_type(adj); @@ -919,6 +919,103 @@ float control_shift_weight_mult(sys::state& state, dcon::province_adjacency_id a return total_multiplier; } +float trade_route_control_propagation( + sys::state const& state, + ve::vectorizable_buffer const& control_buffer, + ve::vectorizable_buffer const& coastal_capital_buffer, + dcon::trade_route_id trid +) { + auto distance = state.world.trade_route_get_distance_km(trid); + auto A_market = state.world.trade_route_get_origin(trid); + auto B_market = state.world.trade_route_get_target(trid); + auto A_state_instance = state.world.market_get_zone_from_local_market(A_market); + auto B_state_instance = state.world.market_get_zone_from_local_market(B_market); + auto A_owner = state.world.state_instance_get_nation_from_state_ownership(A_state_instance); + auto B_owner = state.world.state_instance_get_nation_from_state_ownership(B_state_instance); + float propagation_multiplier = 0.01f; + auto sphere_A = state.world.nation_get_in_sphere_of(A_owner); + auto sphere_B = state.world.nation_get_in_sphere_of(B_owner); + auto overlord_A = state.world.overlord_get_ruler( + state.world.nation_get_overlord_as_subject(A_owner) + ); + auto overlord_B = state.world.overlord_get_ruler( + state.world.nation_get_overlord_as_subject(B_owner) + ); + auto leader_A = (overlord_A) ? overlord_A : ((sphere_A) ? sphere_A : A_owner); + auto leader_B = (overlord_B) ? overlord_B : ((sphere_B) ? sphere_B : B_owner); + if(leader_A != leader_B) { + return 0.f; + } + if(A_owner != B_owner) { + propagation_multiplier /= 2.f; + } + + auto capital_A = state.world.state_instance_get_capital(A_state_instance); + auto capital_B = state.world.state_instance_get_capital(B_state_instance); + auto capital_A_control = control_buffer.get(capital_A); + auto capital_B_control = control_buffer.get(capital_B); + + // propagate control between ports + if(state.world.trade_route_get_is_sea_route(trid)) { + auto port_A = coastal_capital_buffer.get(A_state_instance); + auto port_B = coastal_capital_buffer.get(B_state_instance); + // check that ports are not occupied + bool interrupted = false; + if( + state.world.province_get_nation_from_province_control(port_A) + != + state.world.province_get_nation_from_province_ownership(port_A) + ) { + interrupted = true; + } + if( + state.world.province_get_nation_from_province_control(port_B) + != + state.world.province_get_nation_from_province_ownership(port_B) + ) { + interrupted = true; + } + + if(!interrupted) { + auto naval_base_level_A = military::state_naval_base_level(state, A_state_instance); + auto naval_base_level_B = military::state_naval_base_level(state, B_state_instance); + auto naval_base_multiplier = (1.f + naval_base_level_A * 2.f + naval_base_level_B * 2.f); + auto naval_shift_of_control = + (capital_B_control - capital_A_control) + * std::min(0.1f, propagation_multiplier / (distance + 1.f) * naval_base_multiplier); + return naval_shift_of_control; + } + } else { + // propagate along land trade routes + + // check that capitals are not occupied + bool interrupted = false; + if( + state.world.province_get_nation_from_province_control(capital_A) + != + state.world.province_get_nation_from_province_ownership(capital_A) + ) { + interrupted = true; + } + if( + state.world.province_get_nation_from_province_control(capital_B) + != + state.world.province_get_nation_from_province_ownership(capital_B) + ) { + interrupted = true; + } + + if(!interrupted) { + auto land_shift_of_control = + (capital_B_control - capital_A_control) + * std::min(0.1f, propagation_multiplier / (distance + 1.f) / 2.f); + return land_shift_of_control; + } + } + + return 0.f; +} + void update_administrative_efficiency(sys::state& state) { // TODO: Allow overriding from LUA //if(lua_alice_api::has_named_function(state, "update_administrative_efficiency")) { @@ -950,99 +1047,11 @@ void update_administrative_efficiency(sys::state& state) { auto B_market = state.world.trade_route_get_target(trid); auto A_state_instance = state.world.market_get_zone_from_local_market(A_market); auto B_state_instance = state.world.market_get_zone_from_local_market(B_market); - auto A_owner = state.world.state_instance_get_nation_from_state_ownership(A_state_instance); - auto B_owner = state.world.state_instance_get_nation_from_state_ownership(B_state_instance); - float propagation_multiplier = 0.01f; - auto sphere_A = state.world.nation_get_in_sphere_of(A_owner); - auto sphere_B = state.world.nation_get_in_sphere_of(B_owner); - auto overlord_A = state.world.overlord_get_ruler( - state.world.nation_get_overlord_as_subject(A_owner) - ); - auto overlord_B = state.world.overlord_get_ruler( - state.world.nation_get_overlord_as_subject(B_owner) - ); - auto leader_A = (overlord_A) ? overlord_A : ((sphere_A) ? sphere_A : A_owner); - auto leader_B = (overlord_B) ? overlord_B : ((sphere_B) ? sphere_B : B_owner); - if(leader_A != leader_B) { - return; - } - if(A_owner != B_owner) { - propagation_multiplier /= 2.f; - } - // propagate control between ports - if(state.world.trade_route_get_is_sea_route(trid)) { - auto port_A = coastal_capital_buffer.get(A_state_instance); - auto port_B = coastal_capital_buffer.get(B_state_instance); - - // check that ports are not occupied - bool interrupted = false; - if( - state.world.province_get_nation_from_province_control(port_A) - != - state.world.province_get_nation_from_province_ownership(port_A) - ) { - interrupted = true; - } - if( - state.world.province_get_nation_from_province_control(port_B) - != - state.world.province_get_nation_from_province_ownership(port_B) - ) { - interrupted = true; - } - - if(!interrupted) { - auto port_A_control = control_buffer.get(port_A); - auto port_B_control = control_buffer.get(port_B); - - auto naval_base_level_A = military::state_naval_base_level(state, A_state_instance); - auto naval_base_level_B = military::state_naval_base_level(state, B_state_instance); - - auto naval_base_multiplier = (1.f + naval_base_level_A * 2.f + naval_base_level_B * 2.f); - - auto naval_shift_of_control = - (port_B_control - port_A_control) - * std::min(0.1f, propagation_multiplier / (distance + 1.f) * naval_base_multiplier); - state.world.province_set_control_scale(port_A, state.world.province_get_control_scale(port_A) + naval_shift_of_control); - state.world.province_set_control_scale(port_B, state.world.province_get_control_scale(port_B) - naval_shift_of_control); - assert(std::isfinite(state.world.province_get_control_scale(port_A))); - assert(std::isfinite(state.world.province_get_control_scale(port_B))); - } - } else { - // propagate along land trade routes - auto capital_A = state.world.state_instance_get_capital(A_state_instance); - auto capital_B = state.world.state_instance_get_capital(B_state_instance); - - // check that capitals are not occupied - bool interrupted = false; - if( - state.world.province_get_nation_from_province_control(capital_A) - != - state.world.province_get_nation_from_province_ownership(capital_A) - ) { - interrupted = true; - } - if( - state.world.province_get_nation_from_province_control(capital_B) - != - state.world.province_get_nation_from_province_ownership(capital_B) - ) { - interrupted = true; - } - - if(!interrupted) { - auto capital_A_control = control_buffer.get(capital_A); - auto capital_B_control = control_buffer.get(capital_B); - - auto land_shift_of_control = - (capital_B_control - capital_A_control) - * std::min(0.1f, propagation_multiplier / (distance + 1.f) / 2.f); - state.world.province_set_control_scale(capital_A, state.world.province_get_control_scale(capital_A) + land_shift_of_control); - state.world.province_set_control_scale(capital_B, state.world.province_get_control_scale(capital_B) - land_shift_of_control); - assert(std::isfinite(state.world.province_get_control_scale(capital_A))); - assert(std::isfinite(state.world.province_get_control_scale(capital_B))); - } - } + auto capital_A = state.world.state_instance_get_capital(A_state_instance); + auto capital_B = state.world.state_instance_get_capital(B_state_instance); + auto shift = trade_route_control_propagation(state, control_buffer, coastal_capital_buffer, trid); + state.world.province_set_control_scale(capital_A, state.world.province_get_control_scale(capital_A) + shift); + state.world.province_set_control_scale(capital_B, state.world.province_get_control_scale(capital_B) - shift); }); // propagate control inside states @@ -1078,7 +1087,7 @@ void update_administrative_efficiency(sys::state& state) { state.world.for_each_province([&](auto pid) { auto total_weight = total_adjacency_weight.get(pid) + 0.00001f; - auto control_to_transfer = control_buffer.get(pid) * 0.9f; + auto control_to_transfer = control_buffer.get(pid) * 0.05f; state.world.province_for_each_province_adjacency(pid, [&](auto adj) { auto other = state.world.province_adjacency_get_connected_provinces(adj, 0); if(other == pid) { diff --git a/src/nations/nations.hpp b/src/nations/nations.hpp index 071f4bcee..e77caf83b 100644 --- a/src/nations/nations.hpp +++ b/src/nations/nations.hpp @@ -174,9 +174,15 @@ void recalculate_markets_distance(sys::state& state); dcon::text_key name_from_tag(sys::state& state, dcon::national_identity_id tag); +float trade_route_control_propagation( + sys::state const& state, + ve::vectorizable_buffer const& control_buffer, + ve::vectorizable_buffer const& coastal_capital_buffer, + dcon::trade_route_id trid, float origin, float target +); void update_administrative_efficiency(sys::state& state); void update_national_administrative_efficiency(sys::state& state); -float admin_cost_of_province(sys::state& state, dcon::province_id pid); +float admin_cost_of_province(sys::state const& state, dcon::province_id pid); float priority_national(sys::state& state, dcon::nation_id n, dcon::factory_type_id ftid); float priority_private(sys::state& state, dcon::nation_id n, dcon::factory_type_id ftid); @@ -331,7 +337,7 @@ void make_uncivilized(sys::state& state, dcon::nation_id n); void run_gc(sys::state& state); -float control_shift_weight_mult(sys::state& state, dcon::province_adjacency_id adj); -float desire_score_province(sys::state& state, dcon::province_id pid); +float control_shift_weight_mult(sys::state const& state, dcon::province_adjacency_id adj); +float desire_score_province(sys::state const& state, dcon::province_id pid); } // namespace nations diff --git a/src/provinces/province.cpp b/src/provinces/province.cpp index c60b216d3..1cd30e087 100644 --- a/src/provinces/province.cpp +++ b/src/provinces/province.cpp @@ -2274,7 +2274,7 @@ float distance_km(sys::state& state, dcon::province_adjacency_id pair) { // direct distance between two provinces; does not pathfind -float direct_distance(sys::state& state, dcon::province_id a, dcon::province_id b) { +float direct_distance(sys::state const& state, dcon::province_id a, dcon::province_id b) { auto apos = state.world.province_get_mid_point_b(a); auto bpos = state.world.province_get_mid_point_b(b); auto dot = (apos.x * bpos.x + apos.y * bpos.y) + apos.z * bpos.z; @@ -2283,7 +2283,7 @@ float direct_distance(sys::state& state, dcon::province_id a, dcon::province_id } -float direct_distance_km(sys::state& state, dcon::province_id a, dcon::province_id b) { +float direct_distance_km(sys::state const& state, dcon::province_id a, dcon::province_id b) { auto apos = state.world.province_get_mid_point_b(a); auto bpos = state.world.province_get_mid_point_b(b); auto dot = (apos.x * bpos.x + apos.y * bpos.y) + apos.z * bpos.z; @@ -2498,7 +2498,7 @@ Generates the path for land trade Allowed to path through sea provinces. Becase there states which can have land connection with other states while being split by sea */ -std::vector make_land_trade_path(sys::state& state, dcon::province_id start, dcon::province_id end) { +std::vector make_land_trade_path(sys::state const& state, dcon::province_id start, dcon::province_id end) { auto adjacency_func = [&](dcon::province_id to, dcon::province_id from, dcon::province_adjacency_id adj) { auto bits = state.world.province_adjacency_get_type(adj); @@ -2608,7 +2608,7 @@ std::vector make_naval_unit_path(sys::state& state, dcon::pro } // for sea trade routes -std::vector make_sea_trade_route_path(sys::state& state, dcon::province_id start, dcon::province_id end) { +std::vector make_sea_trade_route_path(sys::state const& state, dcon::province_id start, dcon::province_id end) { auto adjacency_func = [&](dcon::province_id to, dcon::province_id from, dcon::province_adjacency_id adj) { auto bits = state.world.province_adjacency_get_type(adj); diff --git a/src/provinces/province.hpp b/src/provinces/province.hpp index af26d597d..1ab6a4dd0 100644 --- a/src/provinces/province.hpp +++ b/src/provinces/province.hpp @@ -101,9 +101,9 @@ float distance(sys::state& state, dcon::province_adjacency_id pair); float distance_km(sys::state& state, dcon::province_adjacency_id pair); // direct distance between two provinces; does not pathfind -float direct_distance(sys::state& state, dcon::province_id a, dcon::province_id b); +float direct_distance(sys::state const& state, dcon::province_id a, dcon::province_id b); -float direct_distance_km(sys::state& state, dcon::province_id a, dcon::province_id b); +float direct_distance_km(sys::state const& state, dcon::province_id a, dcon::province_id b); // naval range distance between a port and a sea province. Must take the distance of a naval path instead of direct distance naval_range_data naval_range_distance(sys::state& state, dcon::province_id port_prov, dcon::province_id sea_prov, dcon::nation_id nation_as); @@ -139,7 +139,7 @@ bool make_land_unit_path_province_valid(sys::state& state, dcon::nation_id natio std::vector make_land_unit_path(sys::state& state, dcon::province_id start, dcon::province_id end, dcon::nation_id nation_as, dcon::army_id a); // pathfind through non-enemy controlled, not under siege provinces std::vector make_safe_land_path(sys::state& state, dcon::province_id start, dcon::province_id end, dcon::nation_id nation_as); -std::vector make_land_trade_path(sys::state& state, dcon::province_id start, dcon::province_id end); +std::vector make_land_trade_path(sys::state const& state, dcon::province_id start, dcon::province_id end); // creates a path in which only impassable provinces and sea provinces obstructs pathing std::vector make_unowned_land_path(sys::state& state, dcon::province_id start, dcon::province_id end); @@ -152,7 +152,7 @@ bool make_naval_unit_path_province_valid(sys::state& state, dcon::nation_id nati // naval unit pathfinding; start and end provinces may be land provinces; function assumes you have naval access to both std::vector make_naval_unit_path(sys::state& state, dcon::province_id start, dcon::province_id end, dcon::nation_id nation_as); //for sea trade routes -std::vector make_sea_trade_route_path(sys::state& state, dcon::province_id start, dcon::province_id end); +std::vector make_sea_trade_route_path(sys::state const& state, dcon::province_id start, dcon::province_id end); //naval retreats std::vector make_naval_retreat_path(sys::state& state, dcon::nation_id nation_as, dcon::province_id start); // For clicking on the retreat button, or forced retreats diff --git a/src/provinces/province_templates.hpp b/src/provinces/province_templates.hpp index ec175321c..f1ad4bffa 100644 --- a/src/provinces/province_templates.hpp +++ b/src/provinces/province_templates.hpp @@ -144,7 +144,7 @@ struct path_node_heuristic { // ProvFunc: Lambda which takes a province_id as parameter and returns a bool. Decides if the given province is passable from any direction // MovementCostFunc: Lambda which takes the following as parameters (to_prov, from_prov, adjacency, distance) and returns a float. The returned value is used as movement cost in pathfinding template -std::vector make_path_to_prov(sys::state& state, dcon::province_id start, dcon::province_id end, AdjFunc&& adj_func, ProvFunc&& prov_func, MovementCostFunc&& movementcost_func) { +std::vector make_path_to_prov(sys::state const& state, dcon::province_id start, dcon::province_id end, AdjFunc&& adj_func, ProvFunc&& prov_func, MovementCostFunc&& movementcost_func) { // uses an A* implementation with direct distance as heuristic @@ -262,7 +262,7 @@ std::vector make_path_to_prov(sys::state& state, dcon::provin // ProvFunc: Lambda which takes a province_id as parameter and returns a bool. Decides if the given province is passable from any direction // MovementCostFunc: Lambda which takes the following as parameters (to_prov, from_prov, adjacency, distance) and returns a float. The returned value is used as movement cost in pathfinding template -std::vector make_path_to_prov_fast(sys::state& state, dcon::province_id start, dcon::province_id end, AdjFunc&& adj_func, ProvFunc&& prov_func, MovementCostFunc&& movementcost_func) { +std::vector make_path_to_prov_fast(sys::state const& state, dcon::province_id start, dcon::province_id end, AdjFunc&& adj_func, ProvFunc&& prov_func, MovementCostFunc&& movementcost_func) { std::vector path_heap; auto origins_vector = ve::vectorizable_buffer(state.world.province_size());