The Battle for Wesnoth  1.17.10+dev
game_state.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2022
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "game_state.hpp"
17 
18 #include "actions/undo.hpp"
19 #include "game_board.hpp"
20 #include "game_data.hpp"
21 #include "game_events/manager.hpp"
22 #include "log.hpp"
23 #include "map/map.hpp"
24 #include "pathfind/pathfind.hpp"
25 #include "pathfind/teleport.hpp"
26 #include "play_controller.hpp"
27 #include "preferences/game.hpp"
28 #include "random_deterministic.hpp"
29 #include "reports.hpp"
31 #include "synced_context.hpp"
32 #include "teambuilder.hpp"
33 #include "units/unit.hpp"
34 #include "whiteboard/manager.hpp"
36 #include "side_controller.hpp"
37 
38 #include <functional>
39 #include <SDL2/SDL_timer.h>
40 
41 #include <algorithm>
42 #include <set>
43 
44 static lg::log_domain log_engine("engine");
45 #define LOG_NG LOG_STREAM(info, log_engine)
46 #define DBG_NG LOG_STREAM(debug, log_engine)
47 #define ERR_NG LOG_STREAM(err, log_engine)
48 
50  : gamedata_(level)
51  , board_(level)
52  , tod_manager_(level)
53  , pathfind_manager_(new pathfind::manager(level))
54  , reports_(new reports())
55  , lua_kernel_(new game_lua_kernel(*this, pc, *reports_))
56  , ai_manager_()
57  , events_manager_(new game_events::manager())
58  // TODO: this construct units (in dimiss undo action) but resrouces:: are not available yet,
59  // so we might want to move the innitialisation of undo_stack_ to game_state::init
60  , undo_stack_(new actions::undo_list(level.child("undo_stack")))
61  , player_number_(level["playing_team"].to_int() + 1)
62  , next_player_number_(level["next_player_number"].to_int(player_number_ + 1))
63  , do_healing_(level["do_healing"].to_bool(false))
64  , init_side_done_(level["init_side_done"].to_bool(false))
65  , start_event_fired_(!level["playing_team"].empty())
66  , server_request_number_(level["server_request_number"].to_int())
67  , first_human_team_(-1)
68 {
69  lua_kernel_->load_core();
70  if(const config& endlevel_cfg = level.child("end_level_data")) {
71  end_level_data el_data;
72  el_data.read(endlevel_cfg);
73  el_data.transient.carryover_report = false;
74  end_level_data_ = el_data;
75  }
76 }
77 
79  : gamedata_(level)
80  , board_(board)
81  , tod_manager_(level)
82  , pathfind_manager_(new pathfind::manager(level))
83  , reports_(new reports())
84  , lua_kernel_(new game_lua_kernel(*this, pc, *reports_))
85  , ai_manager_()
86  , events_manager_(new game_events::manager())
87  , player_number_(level["playing_team"].to_int() + 1)
88  , next_player_number_(level["next_player_number"].to_int(player_number_ + 1))
89  , do_healing_(level["do_healing"].to_bool(false))
90  , end_level_data_()
91  , init_side_done_(level["init_side_done"].to_bool(false))
92  , start_event_fired_(!level["playing_team"].empty())
93  , server_request_number_(level["server_request_number"].to_int())
94  , first_human_team_(-1)
95 {
96  lua_kernel_->load_core();
97  events_manager_->read_scenario(level, *lua_kernel_);
98  if(const config& endlevel_cfg = level.child("end_level_data")) {
99  end_level_data el_data;
100  el_data.read(endlevel_cfg);
101  el_data.transient.carryover_report = false;
102  end_level_data_ = el_data;
103  }
104 }
105 
107 
108 static int placing_score(const config& side, const gamemap& map, const map_location& pos)
109 {
110  int positions = 0, liked = 0;
111  const t_translation::ter_list terrain = t_translation::read_list(side["terrain_liked"].str());
112 
113  for(int i = -8; i != 8; ++i) {
114  for(int j = -8; j != +8; ++j) {
115  const map_location pos2 = pos.plus(i, j);
116  if(map.on_board(pos2)) {
117  ++positions;
118  if(std::count(terrain.begin(),terrain.end(),map[pos2])) {
119  ++liked;
120  }
121  }
122  }
123  }
124 
125  return (100*liked)/positions;
126 }
127 
128 struct placing_info {
129 
131  side(0),
132  score(0),
133  pos()
134  {
135  }
136 
137  int side, score;
139 };
140 
141 static bool operator<(const placing_info& a, const placing_info& b) { return a.score > b.score; }
142 
143 
145 {
146  std::vector<placing_info> placings;
147 
148  int num_pos = board_.map().num_valid_starting_positions();
149 
150  int side_num = 1;
151  for(const config &side : level.child_range("side"))
152  {
153  for(int p = 1; p <= num_pos; ++p) {
154  const map_location& pos = board_.map().starting_position(p);
155  int score = placing_score(side, board_.map(), pos);
156  placing_info obj;
157  obj.side = side_num;
158  obj.score = score;
159  obj.pos = pos;
160  placings.push_back(obj);
161  }
162  ++side_num;
163  }
164 
165  std::stable_sort(placings.begin(),placings.end());
166  std::set<int> placed;
167  std::set<map_location> positions_taken;
168 
169  for (std::vector<placing_info>::const_iterator i = placings.begin(); i != placings.end() && static_cast<int>(placed.size()) != side_num - 1; ++i) {
170  if(placed.count(i->side) == 0 && positions_taken.count(i->pos) == 0) {
171  placed.insert(i->side);
172  positions_taken.insert(i->pos);
173  board_.map().set_starting_position(i->side,i->pos);
174  LOG_NG << "placing side " << i->side << " at " << i->pos;
175  }
176  }
177 }
178 
180 {
181  events_manager_->read_scenario(level, *lua_kernel_);
183  if (level["modify_placing"].to_bool()) {
184  LOG_NG << "modifying placing...";
186  }
187 
188  LOG_NG << "initialized time of day regions... " << (SDL_GetTicks() - pc.ticks());
189  for (const config &t : level.child_range("time_area")) {
191  }
192 
193  LOG_NG << "initialized teams... " << (SDL_GetTicks() - pc.ticks());
194 
195  board_.teams().resize(level.child_count("side"));
196  if (player_number_ > static_cast<int>(board_.teams().size())) {
197  ERR_NG << "invalid player number " << player_number_ << " #sides=" << board_.teams().size();
198  player_number_ = 1;
199  // in case there are no teams, using player_number_ migh still cause problems later.
200  }
201 
202  std::vector<team_builder> team_builders;
203 
204  // Note this isn't strictly necessary since team_builder declares a move constructor which will
205  // be used if a copy is needed (see the class documentation for why a copy causes crashes), but
206  // it can't hurt to be doubly safe.
207  team_builders.reserve(board_.teams().size());
208 
209  int team_num = 0;
210  for (const config &side : level.child_range("side"))
211  {
212  if (first_human_team_ == -1) {
213  const std::string &controller = side["controller"];
214  if (controller == side_controller::human && side["is_local"].to_bool(true)) {
215  first_human_team_ = team_num;
216  }
217  }
218  ++team_num;
219 
220  team_builders.emplace_back(side, board_.get_team(team_num), level, board_, team_num);
221  team_builders.back().build_team_stage_one();
222  }
223 
224  //Initialize the lua kernel before the units are created.
225  lua_kernel_->initialize(level);
226 
227  {
228  //sync traits of start units and the random start time.
230 
232 
233  for(team_builder& tb : team_builders) {
234  tb.build_team_stage_two();
235  }
236 
237  for(std::size_t i = 0; i < board_.teams().size(); i++) {
238  // Labels from players in your ignore list default to hidden
239  if(preferences::is_ignored(board_.teams()[i].current_player())) {
240  std::string label_cat = "side:" + std::to_string(i + 1);
241  board_.hidden_label_categories().push_back(label_cat);
242  }
243  }
244  }
245 }
246 
248 {
249  lua_kernel_->set_game_display(gd);
250 }
251 
252 void game_state::write(config& cfg) const
253 {
254  cfg["init_side_done"] = init_side_done_;
255  if(gamedata_.phase() == game_data::PLAY) {
256  cfg["playing_team"] = player_number_ - 1;
257  cfg["next_player_number"] = next_player_number_;
258  }
259  cfg["server_request_number"] = server_request_number_;
260  cfg["do_healing"] = do_healing_;
261  //Call the lua save_game functions
262  lua_kernel_->save_game(cfg);
263 
264  //Write the game events.
265  events_manager_->write_events(cfg);
266 
267  //Write the map, unit_map, and teams info
268  board_.write_config(cfg);
269 
270  //Write the tod manager, and time areas
272 
273  //write out the current state of the map
274  cfg.merge_with(pathfind_manager_->to_config());
275 
276  //Write the game data, including wml vars
278 
279  // Preserve the undo stack so that fog/shroud clearing is kept accurate.
280  undo_stack_->write(cfg.add_child("undo_stack"));
281 
282  if(end_level_data_) {
283  end_level_data_->write(cfg.add_child("end_level_data"));
284  }
285 }
286 
287 namespace {
288  struct castle_cost_calculator : pathfind::cost_calculator
289  {
290  castle_cost_calculator(const gamemap& map, const team & view_team) :
291  map_(map),
292  viewer_(view_team),
293  use_shroud_(view_team.uses_shroud())
294  {}
295 
296  virtual double cost(const map_location& loc, const double) const
297  {
298  if(!map_.is_castle(loc))
299  return 10000;
300 
301  if ( use_shroud_ && viewer_.shrouded(loc) )
302  return 10000;
303 
304  return 1;
305  }
306 
307  private:
308  const gamemap& map_;
309  const team& viewer_;
310  const bool use_shroud_; // Allows faster checks when shroud is disabled.
311  };
312 }//anonymous namespace
313 
314 
315 /**
316  * Checks to see if a leader at @a leader_loc could recruit somewhere.
317  * This takes into account terrain, shroud (for side @a side), and the presence
318  * of visible units.
319  * The behavior for an invalid @a side is subject to change for future needs.
320  */
321 bool game_state::can_recruit_from(const map_location& leader_loc, int side) const
322 {
323  const gamemap& map = board_.map();
324 
325  if(!map.is_keep(leader_loc)) {
326  return false;
327  }
328 
329  try {
330  return pathfind::find_vacant_tile(leader_loc, pathfind::VACANT_CASTLE, nullptr, &board_.get_team(side))
332  } catch(const std::out_of_range&) {
333  // Invalid side specified.
334  // Currently this cannot happen, but it could conceivably be used in
335  // the future to request that shroud and visibility be ignored. Until
336  // that comes to pass, just return.
337  return false;
338  }
339 }
340 
341 bool game_state::can_recruit_from(const unit& leader) const
342 {
343  return can_recruit_from(leader.get_location(), leader.side());
344 }
345 
346 
347 /**
348  * Checks to see if a leader at @a leader_loc could recruit on @a recruit_loc.
349  * This takes into account terrain, shroud (for side @a side), and whether or
350  * not there is already a visible unit at recruit_loc.
351  * The behavior for an invalid @a side is subject to change for future needs.
352  */
353 bool game_state::can_recruit_on(const map_location& leader_loc, const map_location& recruit_loc, int side) const
354 {
355  const gamemap& map = board_.map();
356 
357  if(!map.is_castle(recruit_loc)) {
358  return false;
359  }
360 
361  if(!map.is_keep(leader_loc)) {
362  return false;
363  }
364 
365  try {
366  const team& view_team = board_.get_team(side);
367 
368  if(view_team.shrouded(recruit_loc)) {
369  return false;
370  }
371 
372  if(board_.has_visible_unit(recruit_loc, view_team)) {
373  return false;
374  }
375 
376  castle_cost_calculator calc(map, view_team);
377 
378  // The limit computed in the third argument is more than enough for
379  // any convex castle on the map. Strictly speaking it could be
380  // reduced to sqrt(map.w()**2 + map.h()**2).
382  pathfind::a_star_search(leader_loc, recruit_loc, map.w() + map.h(), calc, map.w(), map.h());
383 
384  return !rt.steps.empty();
385  } catch(const std::out_of_range&) {
386  // Invalid side specified.
387  // Currently this cannot happen, but it could conceivably be used in
388  // the future to request that shroud and visibility be ignored. Until
389  // that comes to pass, just return.
390  return false;
391  }
392 }
393 
394 bool game_state::can_recruit_on(const unit& leader, const map_location& recruit_loc) const
395 {
396  return can_recruit_on(leader.get_location(), recruit_loc, leader.side());
397 }
398 
400 {
401  unit_map::const_iterator leader = board_.units().find(hex);
402  if ( leader != board_.units().end() ) {
403  return leader->can_recruit() && leader->side() == side && can_recruit_from(*leader);
404  } else {
405  // Look for a leader who can recruit on last_hex.
406  for ( leader = board_.units().begin(); leader != board_.units().end(); ++leader) {
407  if ( leader->can_recruit() && leader->side() == side && can_recruit_on(*leader, hex) ) {
408  return true;
409  }
410  }
411  }
412  // No leader found who can recruit at last_hex.
413  return false;
414 }
415 
417 {
418  return this->events_manager_->wml_menu_items();
419 }
420 
422 {
423  return this->events_manager_->wml_menu_items();
424 }
425 
426 
427 namespace
428 {
429  //not really a 'choice' we just need to make sure to inform the server about this.
430 class add_side_wml_choice : public synced_context::server_choice
431 {
432 public:
433  add_side_wml_choice()
434  {
435  }
436 
437  /** We are in a game with no mp server and need to do this choice locally */
438  virtual config local_choice() const
439  {
440  return config{};
441  }
442 
443  /** The request which is sent to the mp server. */
444  virtual config request() const
445  {
446  return config{};
447  }
448 
449  virtual const char* name() const
450  {
451  return "add_side_wml";
452  }
453 
454 private:
455 };
456 } // end anon namespace
457 
458 
460 {
461  cfg["side"] = board_.teams().size() + 1;
462  //if we want to also allow setting the controller we must update the server code.
463  cfg["controller"] = side_controller::none;
464  //TODO: is this it? are there caches which must be cleared?
465  board_.teams().emplace_back();
466  board_.teams().back().build(cfg, board_.map(), cfg["gold"].to_int());
467  config choice = synced_context::ask_server_choice(add_side_wml_choice());
468 }
const std::unique_ptr< reports > reports_
Definition: game_state.hpp:50
void read(const config &cfg)
bool is_keep(const map_location &loc) const
Definition: map.cpp:72
const std::unique_ptr< actions::undo_list > undo_stack_
undo_stack_ is never nullptr.
Definition: game_state.hpp:59
Game board class.
Definition: game_board.hpp:52
virtual const std::vector< std::string > & hidden_label_categories() const override
Definition: game_board.hpp:123
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
unit_iterator end()
Definition: map.hpp:429
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:86
std::unique_ptr< game_lua_kernel > lua_kernel_
Definition: game_state.hpp:51
virtual const unit_map & units() const override
Definition: game_board.hpp:113
std::optional< end_level_data > end_level_data_
Definition: game_state.hpp:65
ai::manager ai_manager_
Definition: game_state.hpp:52
This class represents a single unit of a specific type.
Definition: unit.hpp:133
bool is_castle(const map_location &loc) const
Definition: map.cpp:70
map_location starting_position(int side) const
Definition: map.cpp:324
bool can_recruit_on(const map_location &leader_loc, const map_location &recruit_loc, int side) const
Checks to see if a leader at leader_loc could recruit on recruit_loc.
Definition: game_state.cpp:353
#define ERR_NG
Definition: game_state.cpp:47
game_events::wmi_manager & get_wml_menu_items()
Definition: game_state.cpp:416
#define a
unsigned child_count(config_key_type key) const
Definition: config.cpp:372
int next_player_number_
Definition: game_state.hpp:61
const randomness::mt_rng & rng() const
Definition: game_data.hpp:68
map_location find_vacant_tile(const map_location &loc, VACANT_TILE_TYPE vacancy, const unit *pass_check, const team *shroud_check, const game_board *board)
Function that will find a location on the board that is as near to loc as possible, but which is unoccupied by any units.
Definition: pathfind.cpp:54
int first_human_team_
Definition: game_state.hpp:75
child_itors child_range(config_key_type key)
Definition: config.cpp:344
int server_request_number_
Definition: game_state.hpp:69
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
void resolve_random(randomness::rng &r)
handles random_start_time, should be called before the game starts.
Definition: tod_manager.cpp:69
virtual const gamemap & map() const override
Definition: game_board.hpp:103
unit_iterator begin()
Definition: map.hpp:419
bool do_healing_
True if healing should be done at the beginning of the next side turn.
Definition: game_state.hpp:63
bool init_side_done_
Definition: game_state.hpp:66
const std::unique_ptr< game_events::manager > events_manager_
Definition: game_state.hpp:53
Unit and team statistics.
#define b
void merge_with(const config &c)
Merge config &#39;c&#39; into this config, overwriting this config&#39;s values.
Definition: config.cpp:1224
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:75
team & get_team(int i)
Definition: game_board.hpp:98
map_location plus(int x_diff, int y_diff) const
Definition: location.hpp:160
std::vector< map_location > steps
Definition: pathfind.hpp:135
int w() const
Effective map width.
Definition: map.hpp:50
bool uses_shroud() const
Definition: team.hpp:305
bool can_recruit_from(const map_location &leader_loc, int side) const
Checks to see if a leader at leader_loc could recruit somewhere.
Definition: game_state.cpp:321
RAII class to use rng_deterministic in the current scope.
Structure which holds a single route between one location and another.
Definition: pathfind.hpp:132
Encapsulates the map of the game.
Definition: map.hpp:171
tod_manager tod_manager_
Definition: game_state.hpp:48
config to_config() const
Definition: tod_manager.cpp:98
std::unique_ptr< pathfind::manager > pathfind_manager_
Definition: game_state.hpp:49
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition: map.cpp:380
bool carryover_report
Should a summary of the scenario outcome be displayed?
static int placing_score(const config &side, const gamemap &map, const map_location &pos)
Definition: game_state.cpp:108
bool has_visible_unit(const map_location &loc, const team &team, bool see_all=false) const
Definition: game_board.cpp:198
bool is_ignored(const std::string &nick)
Definition: game.cpp:277
#define LOG_NG
Definition: game_state.cpp:45
Encapsulates the map of the game.
Definition: location.hpp:38
Domain specific events.
unit_iterator find(std::size_t id)
Definition: map.cpp:301
bool shrouded(const map_location &loc) const
Definition: team.cpp:648
transient_end_level transient
std::size_t i
Definition: function.cpp:967
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:335
mock_party p
static bool operator<(const placing_info &a, const placing_info &b)
Definition: game_state.cpp:141
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:385
game_data gamedata_
Definition: game_state.hpp:46
Additional information on the game outcome which can be provided by WML.
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:61
void init(const config &level, play_controller &)
Definition: game_state.cpp:179
config & add_child(config_key_type key)
Definition: config.cpp:514
void write_config(config &cfg) const
Definition: game_board.cpp:380
bool side_can_recruit_on(int side, map_location loc) const
Checks if any of the sides leaders can recruit at a location.
Definition: game_state.cpp:399
void set_game_display(game_display *)
Definition: game_state.cpp:247
double t
Definition: astarsearch.cpp:65
void add_time_area(const gamemap &map, const config &cfg)
Adds a new local time area from config, making it follow its own time-of-day sequence.
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1358
game_board board_
Definition: game_state.hpp:47
map_location pos
Definition: game_state.cpp:138
Various functions that implement the undoing (and redoing) of in-game commands.
Standard logging facilities (interface).
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
int player_number_
Definition: game_state.hpp:60
static const map_location & null_location()
Definition: location.hpp:81
static lg::log_domain log_engine("engine")
int side() const
The side this unit belongs to.
Definition: unit.hpp:346
void place_sides_in_preferred_locations(const config &level)
Definition: game_state.cpp:144
game_state(const config &level, play_controller &)
Definition: game_state.cpp:49
void write_snapshot(config &cfg) const
Definition: game_data.cpp:127
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator &calc, const std::size_t width, const std::size_t height, const teleport_map *teleports, bool border)
PHASE phase() const
Definition: game_data.hpp:79
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
void write(config &cfg) const
Definition: game_state.cpp:252
int h() const
Effective map height.
Definition: map.hpp:53
This module contains various pathfinding functions and utilities.
void add_side_wml(config cfg)
creates a new side during a game.
Definition: game_state.cpp:459
bool start_event_fired_
Definition: game_state.hpp:67
ter_list read_list(std::string_view str, const ter_layer filler)
Reads a list of terrains from a string, when reading the.
static config ask_server_choice(const server_choice &)
If we are in a mp game, ask the server, otherwise generate the answer ourselves.