32 #include <unordered_set>
33 #include <glm/gtc/matrix_transform.hpp>
35 #ifdef E3D_BACKEND_OPENGL
39 #elif defined E3D_BACKEND_GLES
40 # include <GLES3/gl3.h>
43 #include "eng3d/state.hpp"
44 #include "eng3d/ui/tooltip.hpp"
45 #include "eng3d/font_sdf.hpp"
46 #include "eng3d/texture.hpp"
47 #include "eng3d/primitive.hpp"
48 #include "eng3d/framebuffer.hpp"
49 #include "eng3d/model.hpp"
50 #include "eng3d/serializer.hpp"
51 #include "eng3d/string.hpp"
52 #include "eng3d/log.hpp"
53 #include "eng3d/orbit_camera.hpp"
54 #include "eng3d/flat_camera.hpp"
55 #include "eng3d/camera.hpp"
56 #include "eng3d/rivers.hpp"
57 #include "eng3d/borders.hpp"
74 return std::hash<size_t>{}(
static_cast<size_t>(id));
85 static inline void get_blob_bounds(std::vector<bool>& visited_provinces,
const Nation& nation,
const Province& province, glm::vec2* min_x, glm::vec2* min_y, glm::vec2* max_x, glm::vec2* max_y) {
86 visited_provinces[province] =
true;
89 auto& neighbour =
g_world.provinces[neighbour_id];
90 if(visited_provinces[neighbour_id])
continue;
91 visited_provinces[neighbour_id] =
true;
92 if(neighbour.controller_id != nation)
continue;
95 if(glm::abs(neighbour.box_area.left - neighbour.box_area.right) >=
g_world.
width / 3.f)
continue;
96 if(glm::abs(neighbour.box_area.top - neighbour.box_area.bottom) >=
g_world.
height / 3.f)
continue;
98 if(neighbour.box_area.left < min_x->x) {
99 min_x->x = neighbour.box_area.left;
100 min_x->y = neighbour.box_area.top;
102 if(neighbour.box_area.top < min_y->y) {
103 min_y->x = neighbour.box_area.left;
104 min_y->y = neighbour.box_area.top;
106 if(neighbour.box_area.right > max_x->x) {
107 max_x->x = neighbour.box_area.right;
108 max_x->y = neighbour.box_area.bottom;
110 if(neighbour.box_area.bottom > max_y->y) {
111 max_y->x = neighbour.box_area.right;
112 max_y->y = neighbour.box_area.bottom;
114 get_blob_bounds(visited_provinces, nation, neighbour, min_x, min_y, max_x, max_y);
121 map_ui_layer{ _map_ui_layer },
122 skybox(0.f, 0.f, 0.f, 255.f * 10.f, 40, false),
123 camera{
std::make_unique<
Eng3D::FlatCamera>(glm::vec2(screen_width, screen_height), glm::vec2(this->gs.world->width, this->gs.world->height)) },
124 map_font{
std::make_unique<
Eng3D::FontSDF>(
"fonts/cinzel_sdf/cinzel") }
128 map_render = std::make_unique<MapRender>(this->gs, *
this);
140 for(
const auto& building_type : this->gs.
world->building_types) {
141 const auto path =
string_format(
"models/building_types/%s.obj", building_type.ref_name.c_str());
145 for(
const auto& unit_type : this->gs.
world->unit_types) {
146 const auto path =
string_format(
"models/unit_types/%s.obj", unit_type.ref_name.c_str());
157 for(
size_t i = 0; i < this->gs.
world->provinces.size(); i++) {
174 const auto& province = this->gs.
world->provinces[province_id];
179 std::vector<bool> visited_provinces(this->
world.provinces.size(),
false);
180 get_blob_bounds(visited_provinces, nation, province, &min_point_x, &min_point_y, &max_point_x, &max_point_y);
183 const glm::vec2 center(
184 min_point_x.x + (max_point_x.x - min_point_x.x) / 2.f,
185 min_point_y.y + (max_point_y.y - min_point_y.y) / 2.f
187 std::vector<glm::vec2> candidates{ max_point_x, max_point_y, min_point_x, min_point_y };
188 glm::vec2 curve_control_point = max_point_x;
189 for(
const auto& candidate : candidates) {
190 if(glm::abs(candidate.x - center.x) < glm::abs(curve_control_point.x - center.x)) {
191 curve_control_point.x = candidate.x;
192 }
else if(glm::abs(candidate.y - center.y) < glm::abs(curve_control_point.y - center.y)) {
193 curve_control_point.y = candidate.y;
198 glm::vec2 farthest_positive = min_point_y, farthest_negative = max_point_y;
199 for(
const auto& candidate : candidates) {
200 if(glm::abs(candidate.x - center.x) > glm::abs(farthest_positive.x - center.x)) {
201 farthest_positive.x = candidate.x;
202 }
else if(glm::abs(candidate.y - center.y) > glm::abs(farthest_positive.y - center.y)) {
203 farthest_positive.y = candidate.y;
206 if(glm::abs(candidate.x - center.x) > glm::abs(farthest_negative.x - center.x)) {
207 farthest_negative.x = candidate.x;
208 }
else if(glm::abs(candidate.y - center.y) > glm::abs(farthest_negative.y - center.y)) {
209 farthest_negative.y = candidate.y;
213 const glm::vec2 middle(
214 min_point_y.x + (max_point_y.x - min_point_y.x) / 2.f,
215 min_point_y.y + (max_point_y.y - min_point_y.y) / 2.f
221 auto label = this->map_font->gen_text(nation.
get_client_hint().
name, min_point_y, max_point_y, middle, width);
226 #ifndef E3D_TARGET_SWITCH
229 for(
const auto& province : this->gs.
world->provinces) {
235 max_point.y = min_point.y = min_point.y + (max_point.y - min_point.y) * 0.5f;
237 if(glm::length(max_point - min_point) >= this->gs.
world->
width / 3.f) {
238 const uint32_t color = std::byteswap<std::uint32_t>((province.
color & 0x00ffffff) << 8);
243 glm::vec2 center = min_point + (max_point - min_point) * 0.5f;
245 auto label = this->map_font->gen_text(province.
name, min_point, max_point, center, width);
250 for(
const auto& nation : this->gs.
world->nations)
263 std::string str = world.nations[world.provinces[id].controller_id].client_username;
265 str +=
string_format(
"(%s)", world.provinces[
id].ref_name.c_str());
271 std::vector<ProvinceColor> province_color;
272 for(
const auto& province : world.provinces)
274 return province_color;
283 obj_shader = std::make_unique<Eng3D::OpenGL::Program>();
285 obj_shader->attach_shader(*gs.builtin_shaders[
"vs_3d"].get());
286 obj_shader->attach_shader(*gs.builtin_shaders[
"fs_3d"].get());
287 obj_shader->attach_shader(*gs.builtin_shaders[
"fs_lib"].get());
291 tree_shder = std::make_unique<Eng3D::OpenGL::Program>();
293 tree_shder->attach_shader(*gs.builtin_shaders[
"vs_tree"].get());
294 tree_shder->attach_shader(*gs.builtin_shaders[
"fs_tree"].get());
295 tree_shder->attach_shader(*gs.builtin_shaders[
"fs_lib"].get());
301 for(
const auto& terrain_type :
world.terrain_types) {
302 const auto path =
string_format(
"models/trees/%s.fbx", terrain_type.ref_name.c_str());
306 size_t n_provinces = 0;
308 for(
const auto& province :
world.provinces) {
316 for(
auto& simple_model : tree_type_model->simple_models)
322 selector = _selector;
326 mapmode_func = _mapmode_generator;
327 mapmode_tooltip_func = _tooltip_generator;
340 const float n_steps = 8.f;
341 const float step = 90.f;
343 for(
float r = 0.f; r <= (n_steps * step); r += step) {
344 float sin_r = (sin(r +
wind_osc) / 24.f);
347 glm::vec3(((r / step) / n_steps) * 1.5f, sin_r, -2.f),
348 glm::vec2((r / step) / n_steps, 0.f)
350 sin_r = sin(r +
wind_osc + 160.f) / 24.f;
352 glm::vec3(((r / step) / n_steps) * 1.5f, sin_r, -1.f),
353 glm::vec2((r / step) / n_steps, 1.f)
363 std::vector<ProvinceColor> province_colors = mapmode_func(*this->gs.
world);
376 if(!province_units.empty()) {
377 auto total_stack_size = 0.f;
378 for(
const auto unit_id : province_units) {
380 total_stack_size += unit.size;
386 if(!unit.on_battle) {
394 if(map.
map_render->options.units.used && !province_units.empty()) {
396 const auto prov_pos = province.
get_pos();
397 auto model =
glm::translate(glm::mat4(1.f), glm::vec3(prov_pos.x, prov_pos.y, 0.f));
398 if(unit.has_target_province()) {
399 const auto& unit_target = gs.
world->provinces[unit.get_target_province_id()];
400 const auto target_pos = unit_target.get_pos();
401 const auto dist = glm::sqrt(glm::pow(glm::abs(prov_pos.x - target_pos.x), 2.f) + glm::pow(glm::abs(prov_pos.y - target_pos.y), 2.f));
402 const auto line_model = glm::rotate(model, glm::atan(target_pos.y - prov_pos.y, target_pos.x - prov_pos.x), glm::vec3(0.f, 0.f, 1.f));
403 map.
obj_shader->set_texture(0,
"diffuse_map", *line_tex);
404 map.
obj_shader->set_uniform(
"model", line_model);
407 model = glm::rotate(model, glm::radians(-90.f), glm::vec3(1.f, 0.f, 0.f));
415 const auto projection =
camera->get_projection();
416 const auto view =
camera->get_view();
417 glm::mat4 base_model(1.f);
423 obj_shader->set_uniform(
"projection", projection);
426 const auto map_pos =
camera->get_map_pos();
427 const auto distance_to_map = map_pos.z / this->gs.
world->
width;
428 constexpr
auto small_zoom_factor = 0.07f;
429 if(distance_to_map < small_zoom_factor) {
431 std::vector<float> province_units_y(this->gs.
world->provinces.size(), 0.f);
433 for(
auto& province : this->gs.
world->provinces) {
436 if((this->
map_render->get_province_opt(province) & 0x000000ff) != 0x000000ff)
439 const auto prov_pos = province.
get_pos();
442 const auto cam_pos =
camera->get_world_pos();
443 const auto world_pos =
camera->get_tile_world_pos(prov_pos);
444 const auto world_to_camera = glm::normalize(cam_pos - world_pos) * orbit_camera.radius * 0.001f;
446 if(glm::length(world_pos + world_to_camera) < orbit_camera.radius)
461 if(this->
map_render->options.buildings.used) {
462 for(
const auto& province : this->gs.
world->provinces) {
463 province_units_y[province] += 2.5f;
464 const auto prov_pos = province.
get_pos();
465 for(
const auto& building_type : this->gs.
world->building_types) {
466 if(!province.
buildings[building_type].level)
continue;
467 glm::mat4 model =
glm::translate(base_model, glm::vec3(prov_pos.x, prov_pos.y, 0.f));
468 model = glm::rotate(model, glm::radians(-90.f), glm::vec3(1.f, 0.f, 0.f));
476 for(
auto& province : this->gs.
world->provinces) {
484 auto model = base_model;
486 obj_shader->set_texture(0,
"diffuse_map", *line_tex);
488 dragbox_square.draw();
492 #ifndef E3D_TARGET_SWITCH
503 tree_shder->set_PVM(projection, view, base_model);
509 const auto model = base_model;
512 obj_shader->set_texture(0,
"diffuse_map", *skybox_tex);
520 this->
camera->set_screen(this->gs.width, this->gs.height);
523 void Map::check_left_mouse_release() {
530 this->selector(*gs.
world, *
this, gs.
world->provinces[province_id]);
543 const auto& province = gs.
world->provinces[province_id];
550 void Map::check_right_mouse_release() {
552 auto& province = gs.
world->provinces[province_id];
560 std::sort(province.
nuclei.begin(), province.
nuclei.end());
561 auto last = std::unique(province.
nuclei.begin(), province.
nuclei.end());
569 this->
map_render->request_update_visibility();
578 if(!unit.can_move())
continue;
580 if(unit_prov_id == province.
get_id() || unit.get_target_province_id() == province.
get_id())
585 if(!relation.has_landpass())
continue;
591 if(!entries.empty()) {
592 auto audio = gs.
audio_man.
load(entries[rand() % entries.size()]->get_abs_path());
607 if(this->
camera->get_cursor_map_pos(e.
pos, map_pos))
616 check_left_mouse_release();
618 check_right_mouse_release();
626 if(gs.
map->camera->get_cursor_map_pos(e.
pos, map_pos)) {
627 glm::vec2 current_pos = glm::make_vec2(gs.
map->camera->get_map_pos());
629 gs.
map->camera->set_pos(pos.x, pos.y);
633 if(gs.
map->camera->get_cursor_map_pos(e.
pos, map_pos)) {
634 if(map_pos.x < 0 || map_pos.x >(
int)gs.
world->
width || map_pos.y < 0 || map_pos.y >(
int)gs.
world->
height)
return;
636 auto prov_id =
map_render->get_tile_province_id(map_pos.x, map_pos.y);
637 const std::string text = mapmode_tooltip_func !=
nullptr ? mapmode_tooltip_func(*gs.
world, prov_id) :
"";
639 gs.
map->tooltip->set_text(text);
const std::vector< UnitId > get_selected_units() const
void clear_selected_units()
const std::shared_ptr< Audio > load(const std::string &path)
std::vector< std::shared_ptr< Eng3D::Audio > > sound_queue
std::vector< std::shared_ptr< Eng3D::IO::Asset::Base > > get_multiple_prefix(const Eng3D::IO::Path &path)
Obtains all assets starting with a given prefix.
std::shared_ptr< Eng3D::IO::Asset::Base > get_unique(const Eng3D::IO::Path &path)
Obtaining an unique asset means the "first-found" policy applies.
std::shared_ptr< Eng3D::Model > load(const std::string &path)
void set_texture(int value, const std::string &name, const Eng3D::Texture &texture) const
Eng3D::AudioManager audio_man
Eng3D::ModelManager model_man
static State & get_instance()
Eng3D::TextureManager tex_man
Eng3D::IO::PackageManager package_man
std::shared_ptr< Eng3D::Texture > load(const std::string &path, TextureOptions options=default_options)
Finds a texture in the list of a texture manager if the texture is already in the list we load the sa...
std::shared_ptr< Eng3D::Texture > get_white()
std::unique_ptr< Client > client
Interface::LobbySelectView * select_nation
std::unique_ptr< Map > map
void change_nation(size_t id)
std::unique_ptr< Eng3D::Camera > camera
std::vector< glm::vec2 > tree_spawn_pos
std::unique_ptr< Eng3D::OpenGL::Program > obj_shader
glm::vec2 last_camera_drag_pos
void set_selected_province(bool selected, ProvinceId id)
std::vector< std::shared_ptr< Eng3D::Texture > > nation_flags
void handle_mouse_button(const Eng3D::Event::MouseButton &e)
Map(GameState &gs, const World &world, UI::Group *map_ui_layer, int screen_width, int screen_height)
std::vector< Interface::UnitWidget * > unit_widgets
std::vector< std::unique_ptr< Eng3D::Label3D > > province_labels
void set_selection(selector_func selector)
void set_map_mode(mapmode_generator mapmode_func, mapmode_tooltip tooltip_func)
std::unique_ptr< MapRender > map_render
void set_view(MapView view)
std::vector< std::unique_ptr< Eng3D::Label3D > > nation_labels
std::vector< std::shared_ptr< Eng3D::Texture > > unit_type_icons
void draw_flag(const Eng3D::OpenGL::Program &shader, const Nation &nation)
void handle_mouse_motions(const Eng3D::Event::MouseMotion &e)
void update_nation_label(const Nation &nation)
std::vector< Interface::BattleWidget * > battle_widgets
std::vector< std::shared_ptr< Eng3D::Model > > building_type_models
std::vector< std::shared_ptr< Eng3D::Texture > > building_type_icons
float wind_osc
Wind oscillator (for flags)
ProvinceId selected_province_id
std::vector< std::shared_ptr< Eng3D::Model > > tree_type_models
bool is_drag
Input states.
std::vector< std::shared_ptr< Eng3D::Model > > unit_type_models
void give_province(Province &province)
Gives this nation a specified province (for example on a treaty)
void control_province(Province &province)
std::vector< ProvinceId > owned_provinces
const Nation::ClientHint & get_client_hint() const
A single province, which is used to simulate economy in a "bulk-tiles" way instead of doing economica...
glm::vec2 get_pos() const
std::vector< ProvinceId > neighbour_ids
struct Province::Battle battle
std::vector< NationId > nuclei
TerrainTypeId terrain_type_id
std::vector< Building > buildings
void use_tooltip(Tooltip *tooltip, glm::ivec2 pos)
Grouping to keep widgets together without triggering events.
Roughly a batallion, consisting of approximately 500 soldiers each.
const std::vector< ProvinceId > get_path() const
Eng3D::Freelist< Unit > units
std::vector< ProvinceId > unit_province
std::vector< UnitId > get_province_units(ProvinceId province_id) const
Nation::Relation & get_relation(NationId a, NationId b)
T::Id get_id(const T &obj) const
Get the id of an object, this is a template for all types except for tiles and locally-stored types (...
std::string empty_province_tooltip(const World &, const ProvinceId)
void draw_battles(Map &map, GameState &gs, Province &province)
std::string political_province_tooltip(const World &world, const ProvinceId id)
std::vector< ProvinceColor > political_map_mode(const World &world)
std::function< std::string(const World &world, const ProvinceId id)> mapmode_tooltip
std::function< void(const World &world, Map &map, const Province &province)> selector_func
std::function< std::vector< ProvinceColor >const World &world)> mapmode_generator
std::vector< ProvinceColor > terrain_map_mode(const World &world)
std::string translate(const std::string_view str)
void debug(const std::string_view category, const std::string_view msg)
void warning(const std::string_view category, const std::string_view msg)
std::string string_format(const std::string_view format, Args &&... args)
String formatter.
static Eng3D::Networking::Packet form_packet(const Unit &unit, const Province &province)
constexpr static Color bgr32(uint32_t abgr)
void for_each(const F &lambda) const
void draw(int instances=0) const
void draw(int instances=0) const
constexpr glm::vec2 size() const
Obtains the current size of the rectangle.
constexpr glm::vec2 position() const
Obtains the current position of the rectangle.
constexpr bool contains(glm::vec2 pos) const
Checks if the point is contains the point.
const char * c_str() const
const std::string_view get_string() const
constexpr Id get_id() const
Eng3D::StringRef ref_name
constexpr bool operator()(const ProvinceId &a, const ProvinceId &b) const
std::size_t operator()(const ProvinceId &id) const noexcept