Symphony Of Empires
game_state.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 // Name:
18 // client/game_state.cpp
19 //
20 // Abstract:
21 // Does some important stuff.
22 // ----------------------------------------------------------------------------
23 
24 #include <cstdint>
25 #include <cstdio>
26 #include <string>
27 #include <cstdlib>
28 #include <cstring>
29 #include <sys/types.h>
30 #include <filesystem>
31 
32 #include "eng3d/ui/ui.hpp"
33 #include "eng3d/ui/input.hpp"
34 #include "eng3d/ui/image.hpp"
35 #include "eng3d/ui/label.hpp"
36 #include "eng3d/ui/text.hpp"
37 #include "eng3d/ui/progress_bar.hpp"
38 #include "eng3d/audio.hpp"
39 #include "eng3d/string.hpp"
40 #include "eng3d/event.hpp"
41 #include "eng3d/serializer.hpp"
42 #include "eng3d/material.hpp"
43 #include "eng3d/model.hpp"
44 #include "eng3d/texture.hpp"
45 #include "eng3d/log.hpp"
46 #include "eng3d/camera.hpp"
47 #include "eng3d/interface.hpp"
48 
49 #include "client/game_state.hpp"
50 #include "product.hpp"
51 #include "world.hpp"
52 #include "action.hpp"
60 #include "client/map.hpp"
61 #include "client/map_render.hpp"
64 
67 
68  const auto& capital = this->world->provinces[this->curr_nation->capital_id];
69  map->camera->set_pos(capital.box_area.right, capital.box_area.bottom);
70  map->map_render->request_update_visibility();
71  map->map_render->update();
72 
73  // Make topwindow
74  top_win = static_cast<UI::Widget*>(new Interface::TopWindow(*this));
75  minimap = static_cast<UI::Widget*>(new Interface::Minimap(*this, -400, -200, UI::Origin::LOWER_RIGHT_SCREEN));
76  unit_menu = static_cast<UI::Widget*>(new Interface::SelectedUnitsMenu(*this));
77  Eng3D::Log::debug("game", translate_format("Playing as nation %s", this->curr_nation->ref_name.c_str()));
78  this->curr_nation->ai_do_cmd_troops = true;
79  this->curr_nation->ai_controlled = false;
80  if(this->client != nullptr)
82 }
83 
84 std::shared_ptr<Eng3D::Texture> GameState::get_nation_flag(const Nation& nation) {
85  auto& ideology = this->world->ideologies[nation.ideology_id];
86  std::string path = string_format("gfx/flags/%s_%s.png", nation.ref_name.c_str(), ideology.ref_name.c_str());
87  return this->tex_man.load(this->package_man.get_unique(path));
88 }
89 
90 void handle_popups(std::vector<TreatyId>& displayed_treaties, GameState& gs) {
91  std::scoped_lock lock(gs.world->inbox_mutex);
92  // Check that the event is not already displayed to the user
93  for(auto& msg : gs.curr_nation->inbox)
94  {
95  auto& ibtn = gs.event_tray_grp->make_widget<UI::Image>(0, 0, 24, 24, "gfx/noicon.png");
96  ibtn.set_on_click([&gs, msg](UI::Widget& w) {
97  new Interface::DecisionWindow(gs, msg);
98  w.kill();
99  });
100  ibtn.set_tooltip(msg.title);
101  }
102  gs.curr_nation->inbox.clear();
103 
104  for(auto& treaty : gs.world->treaties) {
105  // Check that the treaty is not already displayed
106  auto iter = std::find(displayed_treaties.begin(), displayed_treaties.end(), treaty.get_id());
107  if(iter != displayed_treaties.end()) continue;
108  if(!treaty.does_participate(*gs.curr_nation)) continue; // Must participate in treaty
109  //new Interface::TreatyChooseWindow(gs, treaty.get_id());
110  displayed_treaties.push_back(treaty);
111  }
112 }
113 
115  world->profiler.start("UI_TICK");
116  ui_ctx.do_tick();
117  world->profiler.stop("UI_TICK");
118 
121  map->update_mapmode();
122 }
123 
126  this->paused = true;
127  while(this->run) {
128  while(this->paused)
129  if(!this->run)
130  break;
131  if(!this->run) break;
132 
133  // Only run the economy simulation of host mode is ON, otherwise don't :-)
134  const std::chrono::milliseconds delta{ ms_delay_speed };
135  if(host_mode) {
136  const auto start_time = std::chrono::system_clock::now();
137  Eng3D::Log::debug("world_thread", "World tick performed!");
138  try {
139  world->do_tick();
140  update_tick = true;
141  } catch(const std::exception& e) {
142  std::scoped_lock lock(render_lock);
143  ui_ctx.prompt("Runtime exception", e.what());
144  Eng3D::Log::error("world_thread", e.what());
145  paused = true;
146  }
147  std::this_thread::sleep_until(start_time + delta);
148  }
149  }
150 }
151 
153  const std::scoped_lock lock(this->audio_man.sound_lock);
154  if(this->audio_man.music_queue.empty()) {
155  auto entries = this->package_man.get_multiple_prefix("sfx/music/ambience");
156  this->audio_man.music_fade_value = 0.f;
157  // Search through all the music in 'music/ambience' and picks a random
158  if(!entries.empty()) {
159  const int music_index = rand() % entries.size();
160  auto audio = this->audio_man.load(entries[music_index]->get_abs_path());
161  this->audio_man.music_queue.push_back(audio);
162  Eng3D::Log::debug("music", "Now playing music file " + entries[music_index]->get_abs_path());
163  }
164  }
165 }
166 
167 // Loads the world
169  this->world = &World::get_instance();
170  this->world->init_lua();
171  this->world->load_initial();
172  this->load_progress = 0.0f;
174  this->world->events.clear();
175  this->world->load_mod();
176  this->load_progress = 0.1f;
177  this->loaded_world = true;
178 }
179 
181  if(map != nullptr) map->camera->set_screen(width, height);
182 }
183 
185  if(e.hold) {
186  if(show_ui) {
188  if(ui_ctx.check_click(e.pos)) {
189  const std::scoped_lock lock(audio_man.sound_lock);
190  auto entries = package_man.get_multiple_prefix("sfx/click");
191  if(!entries.empty()) {
192  auto audio = audio_man.load(entries[rand() % entries.size()]->get_abs_path());
193  audio_man.sound_queue.push_back(audio);
194  }
195  return;
196  }
197  }
198  }
199 
201  map->handle_mouse_button(e);
202 
204  input.middle_mouse_down = true;
205  } else {
209  return;
210  // if(ui_ctx.check_hover(e.pos))
211  // return;
212  }
213 
214  if(current_mode != MapMode::NO_MAP) // Map
215  map->handle_mouse_button(e);
217  input.middle_mouse_down = false;
218  }
219  }
220 }
221 
223  if(this->show_ui) {
224  this->ui_ctx.set_cursor_pos(e.pos);
225  this->ui_ctx.check_drag(e.pos);
226  if (this->ui_ctx.check_hover(e.pos))
227  return;
228  }
229 
231  map->handle_mouse_motions(e);
232 }
233 
235  if(show_ui) {
237  if(ui_ctx.check_wheel(e.pos, e.wheel.y * 6)) return;
238  }
239 
241  map->camera->move(0.f, 0.f, -e.wheel.y * delta_time * 120.f);
242 }
243 
245  if(e.hold) {
246  switch(e.type) {
248  show_ui = !show_ui;
249  break;
252  if(profiler_view) {
253  profiler_view->kill();
254  profiler_view = nullptr;
255  }
257  }
258  break;
260  this->reload_shaders();
261  if(this->map != nullptr)
262  this->map->reload_shaders();
263  const std::scoped_lock lock(audio_man.sound_lock);
264  audio_man.music_queue.clear();
265  } break;
267  world->lua.invoke_registered_callback("ai_settings_window_invoke");
268  break;
270  if(editor) break;
272  paused = !paused;
273  if(paused)
274  ui_ctx.prompt("Control", "Unpaused");
275  else
276  ui_ctx.prompt("Control", "Paused");
277  }
278  break;
280  ui_ctx.check_text_input(nullptr);
281  break;
283  if(map && map->camera)
284  map->camera->move(0.f, -1.f, 0.f);
285  break;
287  if(map && map->camera)
288  map->camera->move(0.f, 1.f, 0.f);
289  break;
291  if(map && map->camera)
292  map->camera->move(-1.f, 0.f, 0.f);
293  break;
295  if(map && map->camera)
296  map->camera->move(1.f, 0.f, 0.f);
297  break;
298  default: break;
299  }
300  }
301 }
302 
303 // Get the list of paths to the packages
304 std::vector<std::string> parse_arguments(int argc, char** argv) {
305  std::vector<std::string> pkg_paths;
306  for(int i = 1; i < argc; i++) {
307  std::string arg = std::string(argv[i]);
308  if(arg == "--mod") {
309  i++;
310  if(i >= argc)
311  CXX_THROW(std::runtime_error, translate("Expected an absolute path after --mod"));
312  arg = std::string(argv[i]);
313  pkg_paths.push_back(arg);
314  }
315  }
316  return pkg_paths;
317 }
318 
319 // Setup the loading screen when starting the game
321  auto map_layer = new UI::Group(0, 0);
322  map_layer->managed = false;
323 
324  auto *bg_img = new UI::Image(-(gs.width / 2.f), -(gs.height / 2.f), gs.width, gs.height);
325  bg_img->origin = UI::Origin::CENTER_SCREEN;
326  auto load_screen_entries = gs.package_man.get_multiple_prefix("gfx/load_screen");
327  if(!load_screen_entries.empty())
328  bg_img->current_texture = gs.tex_man.load(load_screen_entries[rand() % load_screen_entries.size()]->get_abs_path());
329 
330  auto* load_pbar = new UI::ProgressBar(0, -24, gs.width, 24, 0.f, 1.f);
331  load_pbar->set_text(translate("Initializing game resources"));
332  load_pbar->origin = UI::Origin::LOWER_LEFT_SCREEN;
333  load_pbar->text_color = Eng3D::Color(1.f, 1.f, 1.f);
334  load_pbar->on_update = ([&gs, load_pbar] (UI::Widget&) {
335  load_pbar->set_value(gs.load_progress);
336  });
337 
338  auto mod_logo_tex = gs.tex_man.load(gs.package_man.get_unique("gfx/mod_logo.png"));
339  new UI::Image(0, 0, mod_logo_tex->width, mod_logo_tex->height, mod_logo_tex);
340 }
341 
342 // Load the world and show the loading screen ui
343 void startup(GameState& gs) {
344  // After loading everything initialize the gamestate initial properties
345  // Call update_on_tick on start of the gamestate
346  gs.update_tick = true;
347  gs.in_game = false;
348  gs.input = Input();
349  gs.run = true;
350  gs.loaded_world = false;
351  gs.loaded_map = false;
352  gs.load_progress = 0.f;
353  std::thread load_world_th(&GameState::load_world_thread, &gs);
354 
355  create_startup_ui(gs);
356  gs.do_run([&gs](){ return gs.loaded_world == false; },
357  ([&gs]() {
358  gs.music_enqueue();
359  // Widgets here SHOULD NOT REQUEST UPON WORLD DATA
360  // so no world lock is needed beforehand
361  gs.do_event();
362  }), ([&gs]() {
363  std::scoped_lock lock(gs.render_lock);
364  gs.clear();
365  gs.ui_ctx.render_all();
366  gs.world->profiler.render_done();
367  })
368  );
369  gs.ui_ctx.clear();
370 
371  auto map_layer = new UI::Group(0, 0);
372  map_layer->managed = false;
373  gs.map = std::make_unique<Map>(gs, *gs.world, map_layer, gs.width, gs.height);
375  gs.map->set_view(MapView::SPHERE_VIEW);
376  gs.map->camera->move(0.f, 50.f, 10.f);
377  gs.loaded_map = true;
378  gs.load_progress = 1.f;
379 
380  load_world_th.join();
381 }
382 
384  for(size_t i = 0; i < gs.production_queue.size(); i++) {
385  const auto& unit_type = gs.world->unit_types[gs.production_queue[i]];
386 
388  bool is_built = false;
389  for(auto& building_type : gs.world->building_types) {
390  for(const auto province_id : gs.curr_nation->controlled_provinces) {
391  auto& province = gs.world->provinces[province_id];
392  auto& building = province.get_buildings()[building_type];
393  // Must not be working on something else
394  if(building.is_working_on_unit()) {
395  is_built = true;
396  gs.client->send(Action::BuildingStartProducingUnit::form_packet(province, building_type, *gs.curr_nation, unit_type));
397  break;
398  }
399  }
400 
401  if(!is_built) break;
402  }
403  if(!is_built) break;
404  gs.production_queue.erase(gs.production_queue.begin() + i);
405  i--;
406  }
407 }
408 
409 void client_update(GameState& gs, std::vector<TreatyId>& displayed_treaties) {
410  gs.music_enqueue();
411  // Locking is very expensive, so we condense everything into a big "if"
412  if(gs.world->world_mutex.try_lock()) {
413  // Required since events may request world data
414  gs.do_event();
415  if(gs.current_mode == MapMode::NORMAL)
416  handle_popups(displayed_treaties, gs);
417 
418  if(gs.update_tick) {
419  gs.update_on_tick();
420  gs.map->map_render->update();
421  gs.update_tick = false;
422 
423  if(gs.current_mode == MapMode::NORMAL) {
425  }
426  }
427 
429  gs.map->camera->move(0.05f, 0.f, 0.f);
430  gs.world->world_mutex.unlock();
431  }
432 }
433 
435  std::scoped_lock render_lock(gs.render_lock);
436  if(gs.current_mode != MapMode::NO_MAP) {
437  const std::scoped_lock update_lock(gs.world->world_mutex);
438  gs.map->camera->update();
439  gs.map->draw();
440  }
441  gs.world->profiler.render_done();
442 }
443 
444 int main(int argc, char** argv) try {
445  std::vector<std::string> pkg_paths = parse_arguments(argc, argv);
446  GameState gs(pkg_paths);
447 
448  startup(gs);
449  // LuaAPI::invoke_registered_callback(gs.world->lua, "map_dev_view_invoke");
450 
451  // Start main menu
452  new Interface::MainMenu(gs);
453 
454  std::vector<TreatyId> displayed_treaties;
455  // Start the world thread
456  std::thread world_th(&GameState::world_thread, &gs);
457  gs.do_run(
458  ([&gs](){ return gs.run == true; }),
459  ([&displayed_treaties, &gs]() { client_update(gs, displayed_treaties); }),
460  ([&gs]() { client_render(gs); })
461  );
462  world_th.join();
463  return 0;
464 } catch(const std::exception& e) {
465  Eng3D::Log::error("Exception thrown", e.what());
466  return -1;
467 }
const std::shared_ptr< Audio > load(const std::string &path)
Definition: audio.cpp:119
std::mutex sound_lock
Definition: audio.hpp:70
float music_fade_value
Definition: audio.hpp:73
std::vector< std::shared_ptr< Eng3D::Audio > > sound_queue
Definition: audio.hpp:71
std::vector< std::shared_ptr< Eng3D::Audio > > music_queue
Definition: audio.hpp:72
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
void reload_shaders()
Definition: state.cpp:365
void do_event()
Definition: state.cpp:427
UI::Context ui_ctx
Definition: state.hpp:128
bool show_ui
Definition: state.hpp:102
Eng3D::AudioManager audio_man
Definition: state.hpp:123
void do_run(std::function< bool(void)> cond, std::function< void(void)> event, std::function< void(void)> render)
Perform the main game loop.
Definition: state.cpp:522
std::atomic< bool > run
Variable telling if the game should quit, honored by most event loops but should be used explicitly i...
Definition: state.hpp:101
Eng3D::TextureManager tex_man
Definition: state.hpp:124
void clear() const
Definition: state.cpp:401
float delta_time
Definition: state.hpp:104
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
void handle_mouse_btn(const Eng3D::Event::MouseButton &) override
Definition: game_state.cpp:184
UI::Widget * event_tray_grp
Definition: game_state.hpp:169
void handle_mouse_wheel(const Eng3D::Event::MouseWheel &) override
Definition: game_state.cpp:234
std::unique_ptr< Client > client
Definition: game_state.hpp:143
void load_world_thread()
Definition: game_state.cpp:168
void play_nation()
Definition: game_state.cpp:65
std::atomic< bool > paused
Definition: game_state.hpp:151
std::mutex render_lock
Definition: game_state.hpp:176
void handle_resize() override
Definition: game_state.cpp:180
UI::Widget * unit_menu
Definition: game_state.hpp:170
void handle_mouse_motion(const Eng3D::Event::MouseMotion &) override
Definition: game_state.cpp:222
Nation * curr_nation
Definition: game_state.hpp:158
bool host_mode
Definition: game_state.hpp:185
void handle_key(const Eng3D::Event::Key &) override
Definition: game_state.cpp:244
std::atomic< bool > loaded_world
Definition: game_state.hpp:146
std::atomic< bool > update_tick
Definition: game_state.hpp:150
bool in_game
Definition: game_state.hpp:183
std::atomic< bool > loaded_map
Definition: game_state.hpp:147
void music_enqueue()
Definition: game_state.cpp:152
UI::Widget * profiler_view
Definition: game_state.hpp:168
Input input
Definition: game_state.hpp:160
UI::Widget * minimap
Definition: game_state.hpp:167
void world_thread()
Definition: game_state.cpp:125
float load_progress
Definition: game_state.hpp:148
void update_on_tick()
Definition: game_state.cpp:114
MapMode current_mode
Definition: game_state.hpp:162
std::unique_ptr< Map > map
Definition: game_state.hpp:159
std::shared_ptr< Eng3D::Texture > get_nation_flag(const Nation &nation)
Definition: game_state.cpp:84
World * world
Definition: game_state.hpp:156
std::atomic< int > ms_delay_speed
Definition: game_state.hpp:152
std::vector< UnitTypeId > production_queue
Definition: game_state.hpp:177
UI::Widget * top_win
Definition: game_state.hpp:165
bool middle_mouse_down
Definition: game_state.hpp:99
bool ai_controlled
Definition: nation.hpp:131
std::vector< ProvinceId > controlled_provinces
Definition: nation.hpp:149
ProvinceId capital_id
Definition: nation.hpp:136
IdeologyId ideology_id
Definition: nation.hpp:137
std::deque< Event > inbox
Definition: nation.hpp:150
bool ai_do_cmd_troops
Definition: nation.hpp:132
void render_all()
Render all widgets.
Definition: ui.cpp:323
void do_tick()
Will call on_tick on all widgets.
Definition: ui.cpp:635
bool check_mouse_released(glm::ivec2 mouse_pos)
Release the dragging of the widget.
Definition: ui.cpp:530
void prompt(const std::string &title, const std::string &text)
Definition: ui.cpp:183
bool check_text_input(const char *input)
Will give keyboard input to Input Widget if one is selected.
Definition: ui.cpp:573
void check_drag(glm::ivec2 mouse_pos)
Check for on_drag events, will move Window widgets with is_pinned = false.
Definition: ui.cpp:555
bool check_wheel(glm::ivec2 mouse_pos, int y)
Check if the mouse is above a widget and scroll widget.
Definition: ui.cpp:625
bool check_hover(glm::ivec2 mouse_pos)
Check for on_hover events If the mouse is above a widget call the widgets on_hover or show its toolti...
Definition: ui.cpp:400
bool check_click(glm::ivec2 mouse_pos)
Check for on_click events. Check if the mouse is above a widget and call the widgets on_click if poss...
Definition: ui.cpp:498
void clear()
Removes all widgets.
Definition: ui.cpp:121
void set_cursor_pos(glm::ivec2 pos)
Definition: ui.cpp:275
Grouping to keep widgets together without triggering events.
Definition: group.hpp:44
Image widget, can display pictures or effects on the screen.
Definition: image.hpp:43
The master widget all the other widgets inherit from, do not use directly instead use one of the many...
Definition: widget.hpp:176
T & make_widget(Targs &&...args)
Definition: widget.hpp:222
virtual void set_on_click(std::function< void(UI::Widget &)> _on_click)
Sets the on_click function of this widget.
Definition: widget.hpp:259
void kill()
Kills the current widget, setting it up for deletion when dead widgets are cleared by the UI context.
Definition: widget.hpp:283
void load_mod()
Definition: world.cpp:590
std::mutex inbox_mutex
Definition: world.hpp:227
void load_initial()
Definition: world.cpp:419
void do_tick()
Definition: world.cpp:753
void init_lua()
Definition: world.cpp:79
std::mutex world_mutex
Definition: world.hpp:225
Eng3D::Profiler profiler
Definition: world.hpp:130
static World & get_instance()
Definition: world.hpp:121
Eng3D::LuaVM lua
Definition: world.hpp:216
void client_update(GameState &gs, std::vector< TreatyId > &displayed_treaties)
Definition: game_state.cpp:409
int main(int argc, char **argv)
Definition: game_state.cpp:444
void create_startup_ui(GameState &gs)
Definition: game_state.cpp:320
void update_production_queue(GameState &gs)
Definition: game_state.cpp:383
void handle_popups(std::vector< TreatyId > &displayed_treaties, GameState &gs)
Definition: game_state.cpp:90
void client_render(GameState &gs)
Definition: game_state.cpp:434
std::vector< std::string > parse_arguments(int argc, char **argv)
Definition: game_state.cpp:304
void startup(GameState &gs)
Definition: game_state.cpp:343
@ DISPLAY_ONLY
@ LOWER_RIGHT_SCREEN
std::string translate(const std::string_view str)
Definition: string.cpp:76
void error(const std::string_view category, const std::string_view msg)
Definition: log.cpp:68
void debug(const std::string_view category, const std::string_view msg)
Definition: log.cpp:58
std::string string_format(const std::string_view format, Args &&... args)
String formatter.
Definition: string.hpp:100
std::string translate_format(const std::string_view format, Args &&... args)
String formatter, with translation.
Definition: string.hpp:128
static Eng3D::Networking::Packet form_packet(const Province &province, const BuildingType &building_type, const Nation &nation, const UnitType &unit_type)
Definition: action.cpp:81
static Eng3D::Networking::Packet form_packet(const Nation &nation)
Definition: action.cpp:73
Primitive color type used through the engine.
Definition: color.hpp:32
Eng3D::Event::Key::Type type
Definition: event.hpp:48
bool hold
Whetever the key is being held.
Definition: event.hpp:50
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 invoke_registered_callback(const std::string &name)
Some UI functions are hardcoded, for example the main menu is hardcoded to appear when the game start...
Definition: luavm.cpp:376
void stop(const std::string &name)
Definition: profiler.cpp:95
void render_done()
Definition: profiler.cpp:112
void start(const std::string &name)
Definition: profiler.cpp:84
const char * c_str() const
Definition: string.hpp:55
Eng3D::StringRef ref_name
Definition: entity.hpp:161
#define CXX_THROW(class,...)
Definition: utils.hpp:98