Symphony Of Empires
map.cpp
Go to the documentation of this file.
1 // Symphony of Empires
2 // Copyright (C) 2021, Symphony of Empires contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // client/map.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <algorithm>
26 #include <cstdlib>
27 #include <cstring>
28 #include <functional>
29 #include <iostream>
30 #include <mutex>
31 #include <memory>
32 #include <unordered_set>
33 #include <glm/gtc/matrix_transform.hpp>
34 
35 #ifdef E3D_BACKEND_OPENGL
36 // Required before GL/gl.h
37 # include <GL/glew.h>
38 # include <GL/gl.h>
39 #elif defined E3D_BACKEND_GLES
40 # include <GLES3/gl3.h>
41 #endif
42 
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"
58 
59 #include "client/map.hpp"
60 #include "client/map_render.hpp"
61 #include "client/game_state.hpp"
66 #include "world.hpp"
67 #include "province.hpp"
69 #include "action.hpp"
70 
71 template<>
72 struct std::hash<ProvinceId> {
73  std::size_t operator()(const ProvinceId& id) const noexcept {
74  return std::hash<size_t>{}(static_cast<size_t>(id));
75  }
76 };
77 
78 template<>
79 struct std::equal_to<ProvinceId> {
80  constexpr bool operator()(const ProvinceId& a, const ProvinceId& b) const {
81  return a == b;
82  }
83 };
84 
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;
87  // Iterate over all neighbours
88  for(const auto neighbour_id : province.neighbour_ids) {
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;
93 
94  // Big provinces not taken in account
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;
97 
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;
101  }
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;
105  }
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;
109  }
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;
113  }
114  get_blob_bounds(visited_provinces, nation, neighbour, min_x, min_y, max_x, max_y);
115  }
116 }
117 
118 Map::Map(GameState& _gs, const World& _world, UI::Group* _map_ui_layer, int screen_width, int screen_height)
119  : gs{ _gs },
120  world{ _world },
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") }
125 {
126  if(this->gen_labels)
127  this->create_labels();
128  map_render = std::make_unique<MapRender>(this->gs, *this);
129 
130  // Set the mapmode
131  extern std::vector<ProvinceColor> terrain_map_mode(const World& world);
132  this->set_map_mode(terrain_map_mode, empty_province_tooltip);
133  Eng3D::Log::debug("game", "Preloading-important stuff");
134 
135  line_tex = gs.tex_man.load(gs.package_man.get_unique("gfx/line_target.png"));
136  skybox_tex = gs.tex_man.load(gs.package_man.get_unique("gfx/space.png"));
137 
138  // Query the initial nation flags
139  nation_flags.resize(this->gs.world->nations.size(), gs.tex_man.get_white());
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());
142  building_type_models.push_back(gs.model_man.load(gs.package_man.get_unique(path)));
143  building_type_icons.push_back(gs.tex_man.get_white());
144  }
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());
147  unit_type_models.push_back(gs.model_man.load(gs.package_man.get_unique(path)));
148  unit_type_icons.push_back(gs.tex_man.get_white());
149  }
150 
151  // Create tooltip for hovering on the map
152  this->tooltip = new UI::Tooltip();
153  this->reload_shaders();
154 
155  this->unit_widgets.resize(this->gs.world->provinces.size());
156  this->battle_widgets.resize(this->gs.world->provinces.size());
157  for(size_t i = 0; i < this->gs.world->provinces.size(); i++) {
158  this->unit_widgets[i] = new Interface::UnitWidget(*this, this->gs, this->map_ui_layer);
159  this->battle_widgets[i] = new Interface::BattleWidget(*this, this->map_ui_layer);
160  }
161 }
162 
164  delete this->tooltip;
165 }
166 
167 void Map::update_nation_label(const Nation& nation) {
168  // No need to update if no labels are displayed!
169  if(!this->gen_labels) return;
170  if(nation.owned_provinces.empty()) return;
171  auto province_id = *nation.owned_provinces.begin();
172  province_id = nation.capital_id;
173 
174  const auto& province = this->gs.world->provinces[province_id];
175  glm::vec2 max_point_x = province.box_area.position() + province.box_area.size();
176  glm::vec2 max_point_y = province.box_area.position() + province.box_area.size();
177  glm::vec2 min_point_x = province.box_area.position();
178  glm::vec2 min_point_y = province.box_area.position();
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);
181 
182  // Find the x and y coordinate that is **nearest** to the center
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
186  );
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;
194  }
195  }
196 
197  // Farthest points from the center
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;
204  }
205 
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;
210  }
211  }
212 
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
216  );
217 
218  float width = 0.74f;
219  // Replace old label
220  assert(this->nation_labels.size() > nation);
221  auto label = this->map_font->gen_text(nation.get_client_hint().name, min_point_y, max_point_y, middle, width);
222  this->nation_labels[nation] = std::move(label);
223 }
224 
226 #ifndef E3D_TARGET_SWITCH
227  // Provinces
228  this->province_labels.clear();
229  for(const auto& province : this->gs.world->provinces) {
230  if (province.name.get_string().empty())
231  continue;
232 
233  glm::vec2 min_point(province.box_area.left, province.box_area.top);
234  glm::vec2 max_point(province.box_area.right, province.box_area.bottom);
235  max_point.y = min_point.y = min_point.y + (max_point.y - min_point.y) * 0.5f;
236 
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);
239  Eng3D::Log::warning("game", string_format("Province %s (color %x) is too big", province.ref_name.c_str(), color));
240  continue;
241  }
242 
243  glm::vec2 center = min_point + (max_point - min_point) * 0.5f;
244  float width = 0.7f;
245  auto label = this->map_font->gen_text(province.name, min_point, max_point, center, width);
246  this->province_labels.push_back(std::move(label));
247  }
248 #endif
249  this->nation_labels.resize(this->gs.world->nations.size()); // Nations
250  for(const auto& nation : this->gs.world->nations)
251  this->update_nation_label(nation);
252 }
253 
254 void Map::set_view(MapView view) {
255  view_mode = view;
256  if(view == MapView::PLANE_VIEW)
257  camera.reset(new Eng3D::FlatCamera(*camera));
258  else if(view == MapView::SPHERE_VIEW)
259  camera.reset(new Eng3D::OrbitCamera(*camera, Eng3D::GLOBE_RADIUS));
260 }
261 
262 std::string political_province_tooltip(const World& world, const ProvinceId id) {
263  std::string str = world.nations[world.provinces[id].controller_id].client_username;
264  if(((GameState&)Eng3D::State::get_instance()).editor)
265  str += string_format("(%s)", world.provinces[id].ref_name.c_str());
266  return str;
267 }
268 
269 // The standard map mode with each province color = country color
270 std::vector<ProvinceColor> political_map_mode(const World& world) {
271  std::vector<ProvinceColor> province_color;
272  for(const auto& province : world.provinces)
273  province_color.emplace_back(province, Eng3D::Color::bgr32(world.nations[province.controller_id].get_client_hint().color));
274  return province_color;
275 }
276 
277 std::string empty_province_tooltip(const World&, const ProvinceId) {
278  return "";
279 }
280 
282  // Shader used for drawing the models using custom model render
283  obj_shader = std::make_unique<Eng3D::OpenGL::Program>();
284  {
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());
288  obj_shader->link();
289  }
290 
291  tree_shder = std::make_unique<Eng3D::OpenGL::Program>();
292  {
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());
296  tree_shder->link();
297  }
298 
299  map_render->reload_shaders();
300  if(this->map_render->options.trees.used) {
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());
303  tree_type_models.push_back(gs.model_man.load(gs.package_man.get_unique(path)));
304  }
305 
306  size_t n_provinces = 0;
307  tree_spawn_pos.reserve(world.provinces.size());
308  for(const auto& province : world.provinces) {
309  if(!world.terrain_types[province.terrain_type_id].is_water_body) {
310  tree_spawn_pos.push_back(province.get_pos());
311  n_provinces++;
312  }
313  }
314 
315  for(auto& tree_type_model : tree_type_models)
316  for(auto& simple_model : tree_type_model->simple_models)
317  simple_model.instancing(*tree_spawn_pos.data(), n_provinces);
318  }
319 }
320 
322  selector = _selector;
323 }
324 
325 void Map::set_map_mode(mapmode_generator _mapmode_generator, mapmode_tooltip _tooltip_generator) {
326  mapmode_func = _mapmode_generator;
327  mapmode_tooltip_func = _tooltip_generator;
328  update_mapmode();
329 }
330 
331 void Map::set_selected_province(bool selected, ProvinceId id) {
332  this->province_selected = selected;
333  this->selected_province_id = id;
334  map_render->request_update_visibility();
335 }
336 
337 void Map::draw_flag(const Eng3D::OpenGL::Program& shader, const Nation& nation) {
338  // Draw a flag that "waves" with some cheap wind effects it
339  // looks nice and it's super cheap to make - only using sine
340  const float n_steps = 8.f; // Resolution of flag in one side (in vertices)
341  const float step = 90.f; // Steps per vertice
343  for(float r = 0.f; r <= (n_steps * step); r += step) {
344  float sin_r = (sin(r + wind_osc) / 24.f);
345  sin_r = sin(r + wind_osc) / 24.f;
346  flag.buffer.push_back(Eng3D::MeshData<glm::vec3, glm::vec2>(
347  glm::vec3(((r / step) / n_steps) * 1.5f, sin_r, -2.f),
348  glm::vec2((r / step) / n_steps, 0.f)
349  ));
350  sin_r = sin(r + wind_osc + 160.f) / 24.f;
351  flag.buffer.push_back(Eng3D::MeshData<glm::vec3, glm::vec2>(
352  glm::vec3(((r / step) / n_steps) * 1.5f, sin_r, -1.f),
353  glm::vec2((r / step) / n_steps, 1.f)
354  ));
355  }
356  flag.upload();
357  shader.set_texture(0, "diffuse_map", *nation_flags[this->gs.world->get_id(nation)]);
358  flag.draw();
359 }
360 
361 // Updates the province color texture with the changed provinces
363  std::vector<ProvinceColor> province_colors = mapmode_func(*this->gs.world);
364  map_render->update_mapmode(province_colors);
365 }
366 
367 void draw_battles(Map& map, GameState& gs, Province& province) {
368  auto line_tex = gs.tex_man.load(gs.package_man.get_unique("gfx/line_target.png"));
369  if(province.battle.active) {
370  map.battle_widgets[province]->set_battle(province);
371  map.battle_widgets[province]->is_render = true;
373  } else {
374  // Units
375  const auto& province_units = gs.world->unit_manager.get_province_units(province);
376  if(!province_units.empty()) {
377  auto total_stack_size = 0.f; // Calculate the total size of our stack
378  for(const auto unit_id : province_units) {
379  const auto& unit = gs.world->unit_manager.units[unit_id];
380  total_stack_size += unit.size;
381  }
382 
383  // Get first/topmost unit
384  auto& unit = gs.world->unit_manager.units[province_units[0]];
385  // Display unit only if not on a battle
386  if(!unit.on_battle) {
387  map.unit_widgets[province]->set_unit(unit);
388  map.unit_widgets[province]->set_size(total_stack_size);
389  map.unit_widgets[province]->is_render = true;
390  }
391  }
392 
393  // Display a single standing unit
394  if(map.map_render->options.units.used && !province_units.empty()) {
395  const auto& unit = gs.world->unit_manager.units[province_units[0]];
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);
405  Eng3D::Square(0.f, 0.f, dist, 0.5f).draw();
406  }
407  model = glm::rotate(model, glm::radians(-90.f), glm::vec3(1.f, 0.f, 0.f));
408  map.obj_shader->set_uniform("model", model);
409  map.unit_type_models[unit.type_id]->draw(*map.obj_shader);
410  }
411  }
412 }
413 
414 void Map::draw() {
415  const auto projection = camera->get_projection();
416  const auto view = camera->get_view();
417  glm::mat4 base_model(1.f);
418 
419  map_render->draw(*camera, view_mode);
420 
422  obj_shader->use();
423  obj_shader->set_uniform("projection", projection);
424  obj_shader->set_uniform("view", view);
425 
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) {
430  // Properly display textures :)
431  std::vector<float> province_units_y(this->gs.world->provinces.size(), 0.f);
432  // Display units that aren't on battles
433  for(auto& province : this->gs.world->provinces) {
434  this->unit_widgets[province]->is_render = false;
435  this->battle_widgets[province]->is_render = false;
436  if((this->map_render->get_province_opt(province) & 0x000000ff) != 0x000000ff)
437  continue;
438 
439  const auto prov_pos = province.get_pos();
440  if(this->view_mode == MapView::SPHERE_VIEW) {
441  const auto& orbit_camera = static_cast<const Eng3D::OrbitCamera&>(*camera);
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;
445  // If outside our range of view we just don't evaluate this province
446  if(glm::length(world_pos + world_to_camera) < orbit_camera.radius)
447  continue;
448  }
449 
450  // Battles
451  draw_battles(*this, gs, province);
452  }
453 
454  // Unit movement lines
455  gs.world->unit_manager.units.for_each([this](Unit& unit) {
456  if(this->gs.curr_nation && unit.owner_id != this->gs.curr_nation->get_id()) return;
457  const auto& path = unit.get_path();
458  });
459 
460  // Buildings
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));
469  obj_shader->set_uniform("model", model);
470  building_type_models[building_type]->draw(*obj_shader);
471  break;
472  }
473  }
474  }
475  } else {
476  for(auto& province : this->gs.world->provinces) {
477  this->unit_widgets[province]->is_render = false;
478  this->battle_widgets[province]->is_render = false;
479  }
480  }
481 
482  // Draw the "drag area" box
483  if(is_drag) {
484  auto model = base_model;
485  obj_shader->set_uniform("model", model);
486  obj_shader->set_texture(0, "diffuse_map", *line_tex);
487  auto dragbox_square = Eng3D::Square(gs.input.drag_coord.x, gs.input.drag_coord.y, gs.input.select_pos.x, gs.input.select_pos.y);
488  dragbox_square.draw();
489  }
490 
491  if(this->gen_labels) {
492 #ifndef E3D_TARGET_SWITCH
493  if(distance_to_map < small_zoom_factor) map_font->draw(province_labels, *camera, view_mode == MapView::SPHERE_VIEW);
494  else map_font->draw(nation_labels, *camera, view_mode == MapView::SPHERE_VIEW);
495 #else
496  if(distance_to_map > small_zoom_factor) map_font->draw(nation_labels, *camera, view_mode == MapView::SPHERE_VIEW);
497 #endif
498  }
499 
500  if(this->map_render->options.trees.used) {
501  // Drawing trees
502  tree_shder->use();
503  tree_shder->set_PVM(projection, view, base_model);
504  tree_type_models[1]->draw(*tree_shder, tree_spawn_pos.size());
505  }
506 
508  // Universe skybox
509  const auto model = base_model;
510  obj_shader->use();
511  obj_shader->set_uniform("model", model);
512  obj_shader->set_texture(0, "diffuse_map", *skybox_tex);
513  skybox.draw();
514  }
515 
516  wind_osc += 0.1f;
517 }
518 
520  this->camera->set_screen(this->gs.width, this->gs.height);
521 }
522 
523 void Map::check_left_mouse_release() {
524  const auto province_id = this->map_render->get_tile_province_id(gs.input.select_pos.x, gs.input.select_pos.y);
525  this->is_drag = false;
526  switch(gs.current_mode) {
527  case MapMode::NORMAL:
528  if(this->selector) {
530  this->selector(*gs.world, *this, gs.world->provinces[province_id]);
531  break;
532  }
533 
534  // Check if we selected an unit
535  this->is_drag = false;
536  if(gs.client_state.get_selected_units().empty()) {
537  // Show province information when clicking on a province
538  new Interface::ProvinceView(gs, gs.world->provinces[province_id]);
539  return;
540  }
541  break;
543  const auto& province = gs.world->provinces[province_id];
545  } break;
546  default: break;
547  }
548 }
549 
550 void Map::check_right_mouse_release() {
551  const auto province_id = this->map_render->get_tile_province_id(gs.input.select_pos);
552  auto& province = gs.world->provinces[province_id];
553  if(gs.editor && gs.current_mode == MapMode::NORMAL) {
554  if(!gs.sea_paint) {
555  if(world.terrain_types[province.terrain_type_id].is_water_body)
556  province.terrain_type_id = (TerrainTypeId)1;
557  gs.curr_nation->control_province(province);
558  gs.curr_nation->give_province(province);
559  province.nuclei.push_back(gs.world->get_id(*gs.curr_nation));
560  std::sort(province.nuclei.begin(), province.nuclei.end());
561  auto last = std::unique(province.nuclei.begin(), province.nuclei.end());
562  province.nuclei.erase(last, province.nuclei.end());
563  } else {
564  province.terrain_type_id = (TerrainTypeId)0;
565  province.nuclei.clear();
566  }
567 
568  this->update_mapmode();
569  this->map_render->request_update_visibility();
570  this->map_render->update();
571  }
572 
574  // Move units
575  for(const auto unit_id : gs.client_state.get_selected_units()) {
576  const auto& unit = gs.world->unit_manager.units[unit_id];
577  auto unit_prov_id = gs.world->unit_manager.unit_province[unit_id];
578  if(!unit.can_move()) continue;
579  // Don't change target if ID is the same...
580  if(unit_prov_id == province.get_id() || unit.get_target_province_id() == province.get_id())
581  continue;
582  if(province.controller_id != gs.curr_nation->get_id()) {
583  // Must either be our ally, have military access with them or be at war
584  const auto& relation = gs.world->get_relation(gs.curr_nation->get_id(), province.controller_id);
585  if(!relation.has_landpass()) continue;
586  }
587  gs.client->send(Action::UnitMove::form_packet(unit, province));
588 
589  const std::scoped_lock lock2(gs.audio_man.sound_lock);
590  auto entries = gs.package_man.get_multiple_prefix("sfx/land_move");
591  if(!entries.empty()) {
592  auto audio = gs.audio_man.load(entries[rand() % entries.size()]->get_abs_path());
593  gs.audio_man.sound_queue.push_back(audio);
594  }
595  }
597 }
598 
600  if(e.hold) {
601  this->is_drag = false;
604  this->is_drag = true;
606  glm::ivec2 map_pos;
607  if(this->camera->get_cursor_map_pos(e.pos, map_pos))
608  this->last_camera_drag_pos = map_pos;
609  }
610  } else {
611  Eng3D::Rectangle map_box(0, 0, gs.world->width, gs.world->height);
612  if(!map_box.contains(gs.input.select_pos))
613  return;
614 
616  check_left_mouse_release();
618  check_right_mouse_release();
619  }
620  }
621 }
622 
624  glm::ivec2 map_pos;
625  if(gs.input.middle_mouse_down) { // Drag the map with middlemouse
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());
628  const glm::vec2 pos = current_pos + last_camera_drag_pos - glm::vec2(map_pos);
629  gs.map->camera->set_pos(pos.x, pos.y);
630  }
631  }
632 
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;
635  gs.input.select_pos = map_pos;
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) : "";
638  if(!text.empty()) {
639  gs.map->tooltip->set_text(text);
640  gs.ui_ctx.use_tooltip(gs.map->tooltip, e.pos);
641  }
642  }
643 }
const std::vector< UnitId > get_selected_units() const
Definition: game_state.hpp:60
void clear_selected_units()
Definition: game_state.hpp:81
const std::shared_ptr< Audio > load(const std::string &path)
Definition: audio.cpp:119
std::mutex sound_lock
Definition: audio.hpp:70
std::vector< std::shared_ptr< Eng3D::Audio > > sound_queue
Definition: audio.hpp:71
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.
Definition: io.cpp:169
std::shared_ptr< Eng3D::IO::Asset::Base > get_unique(const Eng3D::IO::Path &path)
Obtaining an unique asset means the "first-found" policy applies.
Definition: io.cpp:146
std::shared_ptr< Eng3D::Model > load(const std::string &path)
Definition: model.cpp:155
void set_texture(int value, const std::string &name, const Eng3D::Texture &texture) const
UI::Context ui_ctx
Definition: state.hpp:128
Eng3D::AudioManager audio_man
Definition: state.hpp:123
Eng3D::ModelManager model_man
Definition: state.hpp:126
static State & get_instance()
Definition: state.cpp:514
Eng3D::TextureManager tex_man
Definition: state.hpp:124
Eng3D::IO::PackageManager package_man
Definition: state.hpp:122
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...
Definition: texture.cpp:432
std::shared_ptr< Eng3D::Texture > get_white()
Definition: texture.cpp:417
std::unique_ptr< Client > client
Definition: game_state.hpp:143
Nation * curr_nation
Definition: game_state.hpp:158
Interface::LobbySelectView * select_nation
Definition: game_state.hpp:164
Input input
Definition: game_state.hpp:160
bool sea_paint
Definition: game_state.hpp:187
MapMode current_mode
Definition: game_state.hpp:162
std::unique_ptr< Map > map
Definition: game_state.hpp:159
ClientState client_state
Definition: game_state.hpp:161
World * world
Definition: game_state.hpp:156
glm::vec2 select_pos
Definition: game_state.hpp:97
glm::ivec2 drag_coord
Definition: game_state.hpp:98
bool middle_mouse_down
Definition: game_state.hpp:99
void change_nation(size_t id)
Definition: lobby.cpp:126
Definition: map.hpp:89
std::unique_ptr< Eng3D::Camera > camera
Definition: map.hpp:151
void reload_shaders()
Definition: map.cpp:281
MapView view_mode
Definition: map.hpp:123
void create_labels()
Definition: map.cpp:225
void handle_resize()
Definition: map.cpp:519
std::vector< glm::vec2 > tree_spawn_pos
Definition: map.hpp:128
std::unique_ptr< Eng3D::OpenGL::Program > obj_shader
Definition: map.hpp:155
glm::vec2 last_camera_drag_pos
Definition: map.hpp:143
void update_mapmode()
Definition: map.cpp:362
void set_selected_province(bool selected, ProvinceId id)
Definition: map.cpp:331
std::vector< std::shared_ptr< Eng3D::Texture > > nation_flags
Definition: map.hpp:131
void handle_mouse_button(const Eng3D::Event::MouseButton &e)
Definition: map.cpp:599
Map(GameState &gs, const World &world, UI::Group *map_ui_layer, int screen_width, int screen_height)
Definition: map.cpp:118
std::vector< Interface::UnitWidget * > unit_widgets
Definition: map.hpp:153
std::vector< std::unique_ptr< Eng3D::Label3D > > province_labels
Definition: map.hpp:134
void set_selection(selector_func selector)
Definition: map.cpp:321
const World & world
Definition: map.hpp:147
void set_map_mode(mapmode_generator mapmode_func, mapmode_tooltip tooltip_func)
Definition: map.cpp:325
void draw()
Definition: map.cpp:414
UI::Group * map_ui_layer
Definition: map.hpp:149
std::unique_ptr< MapRender > map_render
Definition: map.hpp:152
void set_view(MapView view)
Definition: map.cpp:254
std::vector< std::unique_ptr< Eng3D::Label3D > > nation_labels
Definition: map.hpp:136
std::vector< std::shared_ptr< Eng3D::Texture > > unit_type_icons
Definition: map.hpp:130
void draw_flag(const Eng3D::OpenGL::Program &shader, const Nation &nation)
Definition: map.cpp:337
~Map()
Definition: map.cpp:163
void handle_mouse_motions(const Eng3D::Event::MouseMotion &e)
Definition: map.cpp:623
bool province_selected
Definition: map.hpp:121
bool gen_labels
Definition: map.hpp:137
void update_nation_label(const Nation &nation)
Definition: map.cpp:167
std::vector< Interface::BattleWidget * > battle_widgets
Definition: map.hpp:154
std::vector< std::shared_ptr< Eng3D::Model > > building_type_models
Definition: map.hpp:125
std::vector< std::shared_ptr< Eng3D::Texture > > building_type_icons
Definition: map.hpp:129
float wind_osc
Wind oscillator (for flags)
Definition: map.hpp:140
ProvinceId selected_province_id
Definition: map.hpp:122
std::vector< std::shared_ptr< Eng3D::Model > > tree_type_models
Definition: map.hpp:127
UI::Tooltip * tooltip
Definition: map.hpp:148
bool is_drag
Input states.
Definition: map.hpp:142
std::vector< std::shared_ptr< Eng3D::Model > > unit_type_models
Definition: map.hpp:126
ProvinceId capital_id
Definition: nation.hpp:136
void give_province(Province &province)
Gives this nation a specified province (for example on a treaty)
Definition: nation.cpp:156
void control_province(Province &province)
Definition: nation.cpp:170
std::vector< ProvinceId > owned_provinces
Definition: nation.hpp:148
const Nation::ClientHint & get_client_hint() const
Definition: nation.cpp:202
A single province, which is used to simulate economy in a "bulk-tiles" way instead of doing economica...
Definition: province.hpp:48
glm::vec2 get_pos() const
Definition: province.hpp:70
Eng3D::Rect box_area
Definition: province.hpp:102
std::vector< ProvinceId > neighbour_ids
Definition: province.hpp:124
std::uint32_t color
Definition: province.hpp:97
Eng3D::StringRef name
Definition: province.hpp:96
NationId controller_id
Definition: province.hpp:104
struct Province::Battle battle
std::vector< NationId > nuclei
Definition: province.hpp:123
TerrainTypeId terrain_type_id
Definition: province.hpp:105
std::vector< Building > buildings
Definition: province.hpp:110
void use_tooltip(Tooltip *tooltip, glm::ivec2 pos)
Definition: ui.cpp:580
Grouping to keep widgets together without triggering events.
Definition: group.hpp:44
Tooltip widget, used entirely for hovering purpouses, don't use any other widget for hovering unless ...
Definition: tooltip.hpp:38
Roughly a batallion, consisting of approximately 500 soldiers each.
Definition: unit.hpp:80
const std::vector< ProvinceId > get_path() const
Definition: unit.hpp:111
NationId owner_id
Definition: unit.hpp:128
Eng3D::Freelist< Unit > units
Definition: unit.hpp:173
std::vector< ProvinceId > unit_province
Definition: unit.hpp:175
std::vector< UnitId > get_province_units(ProvinceId province_id) const
Definition: unit.hpp:165
Definition: world.hpp:114
UnitManager unit_manager
Definition: world.hpp:145
Nation::Relation & get_relation(NationId a, NationId b)
Definition: world.hpp:192
size_t width
Definition: world.hpp:220
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 (...
Definition: world.hpp:179
size_t height
Definition: world.hpp:220
std::string empty_province_tooltip(const World &, const ProvinceId)
Definition: map.cpp:277
void draw_battles(Map &map, GameState &gs, Province &province)
Definition: map.cpp:367
std::string political_province_tooltip(const World &world, const ProvinceId id)
Definition: map.cpp:262
std::vector< ProvinceColor > political_map_mode(const World &world)
Definition: map.cpp:270
std::function< std::string(const World &world, const ProvinceId id)> mapmode_tooltip
Definition: map.hpp:81
MapView
Definition: map.hpp:63
std::function< void(const World &world, Map &map, const Province &province)> selector_func
Definition: map.hpp:83
std::function< std::vector< ProvinceColor >const World &world)> mapmode_generator
Definition: map.hpp:82
@ COUNTRY_SELECT
std::vector< ProvinceColor > terrain_map_mode(const World &world)
Definition: minimap.cpp:482
std::string translate(const std::string_view str)
Definition: string.cpp:76
void debug(const std::string_view category, const std::string_view msg)
Definition: log.cpp:58
void warning(const std::string_view category, const std::string_view msg)
Definition: log.cpp:64
std::string string_format(const std::string_view format, Args &&... args)
String formatter.
Definition: string.hpp:100
Definition: utils.hpp:35
static Eng3D::Networking::Packet form_packet(const Unit &unit, const Province &province)
Definition: action.cpp:136
constexpr static Color bgr32(uint32_t abgr)
Definition: color.hpp:101
Eng3D::Event::MouseButton::Type type
Button that is checked.
Definition: event.hpp:60
bool hold
Whetever the button is being held.
Definition: event.hpp:62
void for_each(const F &lambda) const
Definition: freelist.hpp:75
void draw(int instances=0) const
Definition: mesh.hpp:157
void draw(int instances=0) const
Definition: mesh.hpp:205
constexpr glm::vec2 size() const
Obtains the current size of the rectangle.
Definition: rectangle.hpp:68
constexpr glm::vec2 position() const
Obtains the current position of the rectangle.
Definition: rectangle.hpp:86
constexpr bool contains(glm::vec2 pos) const
Checks if the point is contains the point.
Definition: rectangle.hpp:134
const char * c_str() const
Definition: string.hpp:55
const std::string_view get_string() const
Definition: string.cpp:38
constexpr Id get_id() const
Definition: entity.hpp:152
Eng3D::StringRef name
Definition: nation.hpp:84
Eng3D::StringRef ref_name
Definition: entity.hpp:161
constexpr bool operator()(const ProvinceId &a, const ProvinceId &b) const
Definition: map.cpp:80
std::size_t operator()(const ProvinceId &id) const noexcept
Definition: map.cpp:73
World g_world
Definition: world.cpp:59