The Battle for Wesnoth  1.17.0-dev
game_state.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2021
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 
37 #include <functional>
38 #include <SDL2/SDL_timer.h>
39 
40 #include <algorithm>
41 #include <set>
42 
43 static lg::log_domain log_engine("engine");
44 #define LOG_NG LOG_STREAM(info, log_engine)
45 #define DBG_NG LOG_STREAM(debug, log_engine)
46 #define ERR_NG LOG_STREAM(err, log_engine)
47 
49  : gamedata_(level)
50  , board_(level)
51  , tod_manager_(level)
52  , pathfind_manager_(new pathfind::manager(level))
53  , reports_(new reports())
54  , lua_kernel_(new game_lua_kernel(*this, pc, *reports_))
55  , ai_manager_()
56  , events_manager_(new game_events::manager())
57  // TODO: this construct units (in dimiss undo action) but resrouces:: are not available yet,
58  // so we might want to move the innitialisation of undo_stack_ to game_state::init
59  , undo_stack_(new actions::undo_list(level.child("undo_stack")))
60  , player_number_(level["playing_team"].to_int() + 1)
61  , next_player_number_(level["next_player_number"].to_int(player_number_ + 1))
62  , do_healing_(level["do_healing"].to_bool(false))
63  , init_side_done_(level["init_side_done"].to_bool(false))
64  , start_event_fired_(!level["playing_team"].empty())
65  , server_request_number_(level["server_request_number"].to_int())
66  , first_human_team_(-1)
67 {
68  lua_kernel_->load_core();
69  if(const config& endlevel_cfg = level.child("end_level_data")) {
70  end_level_data el_data;
71  el_data.read(endlevel_cfg);
72  el_data.transient.carryover_report = false;
73  end_level_data_ = el_data;
74  }
75 }
76 
78  : gamedata_(level)
79  , board_(board)
80  , tod_manager_(level)
81  , pathfind_manager_(new pathfind::manager(level))
82  , reports_(new reports())
83  , lua_kernel_(new game_lua_kernel(*this, pc, *reports_))
84  , ai_manager_()
85  , events_manager_(new game_events::manager())
86  , player_number_(level["playing_team"].to_int() + 1)
87  , next_player_number_(level["next_player_number"].to_int(player_number_ + 1))
88  , do_healing_(level["do_healing"].to_bool(false))
89  , end_level_data_()
90  , init_side_done_(level["init_side_done"].to_bool(false))
91  , start_event_fired_(!level["playing_team"].empty())
92  , server_request_number_(level["server_request_number"].to_int())
93  , first_human_team_(-1)
94 {
95  lua_kernel_->load_core();
96  events_manager_->read_scenario(level);
97  if(const config& endlevel_cfg = level.child("end_level_data")) {
98  end_level_data el_data;
99  el_data.read(endlevel_cfg);
100  el_data.transient.carryover_report = false;
101  end_level_data_ = el_data;
102  }
103 }
104 
106 
107 static int placing_score(const config& side, const gamemap& map, const map_location& pos)
108 {
109  int positions = 0, liked = 0;
110  const t_translation::ter_list terrain = t_translation::read_list(side["terrain_liked"].str());
111 
112  for(int i = -8; i != 8; ++i) {
113  for(int j = -8; j != +8; ++j) {
114  const map_location pos2 = pos.plus(i, j);
115  if(map.on_board(pos2)) {
116  ++positions;
117  if(std::count(terrain.begin(),terrain.end(),map[pos2])) {
118  ++liked;
119  }
120  }
121  }
122  }
123 
124  return (100*liked)/positions;
125 }
126 
127 struct placing_info {
128 
130  side(0),
131  score(0),
132  pos()
133  {
134  }
135 
136  int side, score;
138 };
139 
140 static bool operator<(const placing_info& a, const placing_info& b) { return a.score > b.score; }
141 
142 
144 {
145  std::vector<placing_info> placings;
146 
147  int num_pos = board_.map().num_valid_starting_positions();
148 
149  int side_num = 1;
150  for(const config &side : level.child_range("side"))
151  {
152  for(int p = 1; p <= num_pos; ++p) {
153  const map_location& pos = board_.map().starting_position(p);
154  int score = placing_score(side, board_.map(), pos);
155  placing_info obj;
156  obj.side = side_num;
157  obj.score = score;
158  obj.pos = pos;
159  placings.push_back(obj);
160  }
161  ++side_num;
162  }
163 
164  std::stable_sort(placings.begin(),placings.end());
165  std::set<int> placed;
166  std::set<map_location> positions_taken;
167 
168  for (std::vector<placing_info>::const_iterator i = placings.begin(); i != placings.end() && static_cast<int>(placed.size()) != side_num - 1; ++i) {
169  if(placed.count(i->side) == 0 && positions_taken.count(i->pos) == 0) {
170  placed.insert(i->side);
171  positions_taken.insert(i->pos);
172  board_.map().set_starting_position(i->side,i->pos);
173  LOG_NG << "placing side " << i->side << " at " << i->pos << std::endl;
174  }
175  }
176 }
177 
179 {
180  events_manager_->read_scenario(level);
182  if (level["modify_placing"].to_bool()) {
183  LOG_NG << "modifying placing..." << std::endl;
185  }
186 
187  LOG_NG << "initialized time of day regions... " << (SDL_GetTicks() - pc.ticks()) << std::endl;
188  for (const config &t : level.child_range("time_area")) {
190  }
191 
192  LOG_NG << "initialized teams... " << (SDL_GetTicks() - pc.ticks()) << std::endl;
193 
194  board_.teams().resize(level.child_count("side"));
195  if (player_number_ > static_cast<int>(board_.teams().size())) {
196  ERR_NG << "invalid player number " << player_number_ << " #sides=" << board_.teams().size() << "\n";
197  player_number_ = 1;
198  // in case there are no teams, using player_number_ migh still cause problems later.
199  }
200 
201  std::vector<team_builder> team_builders;
202 
203  // Note this isn't strictly necessary since team_builder declares a move constructor which will
204  // be used if a copy is needed (see the class documentation for why a copy causes crashes), but
205  // it can't hurt to be doubly safe.
206  team_builders.reserve(board_.teams().size());
207 
208  int team_num = 0;
209  for (const config &side : level.child_range("side"))
210  {
211  if (first_human_team_ == -1) {
212  const std::string &controller = side["controller"];
213  if (controller == "human" && side["is_local"].to_bool(true)) {
214  first_human_team_ = team_num;
215  }
216  }
217  ++team_num;
218 
219  team_builders.emplace_back(side, board_.get_team(team_num), level, board_, team_num);
220  team_builders.back().build_team_stage_one();
221  }
222 
223  //Initialize the lua kernel before the units are created.
224  lua_kernel_->initialize(level);
225 
226  {
227  //sync traits of start units and the random start time.
229 
231 
232  for(team_builder& tb : team_builders) {
233  tb.build_team_stage_two();
234  }
235 
236  for(std::size_t i = 0; i < board_.teams().size(); i++) {
237  // Labels from players in your ignore list default to hidden
238  if(preferences::is_ignored(board_.teams()[i].current_player())) {
239  std::string label_cat = "side:" + std::to_string(i + 1);
240  board_.hidden_label_categories().push_back(label_cat);
241  }
242  }
243  }
244 }
245 
247 {
248  lua_kernel_->set_game_display(gd);
249 }
250 
251 void game_state::write(config& cfg) const
252 {
253  cfg["init_side_done"] = init_side_done_;
254  if(gamedata_.phase() == game_data::PLAY) {
255  cfg["playing_team"] = player_number_ - 1;
256  cfg["next_player_number"] = next_player_number_;
257  }
258  cfg["server_request_number"] = server_request_number_;
259  cfg["do_healing"] = do_healing_;
260  //Call the lua save_game functions
261  lua_kernel_->save_game(cfg);
262 
263  //Write the game events.
264  events_manager_->write_events(cfg);
265 
266  //Write the map, unit_map, and teams info
267  board_.write_config(cfg);
268 
269  //Write the tod manager, and time areas
271 
272  //write out the current state of the map
273  cfg.merge_with(pathfind_manager_->to_config());
274 
275  //Write the game data, including wml vars
277 
278  // Preserve the undo stack so that fog/shroud clearing is kept accurate.
279  undo_stack_->write(cfg.add_child("undo_stack"));
280 
281  if(end_level_data_) {
282  end_level_data_->write(cfg.add_child("end_level_data"));
283  }
284 }
285 
286 namespace {
287  struct castle_cost_calculator : pathfind::cost_calculator
288  {
289  castle_cost_calculator(const gamemap& map, const team & view_team) :
290  map_(map),
291  viewer_(view_team),
292  use_shroud_(view_team.uses_shroud())
293  {}
294 
295  virtual double cost(const map_location& loc, const double) const
296  {
297  if(!map_.is_castle(loc))
298  return 10000;
299 
300  if ( use_shroud_ && viewer_.shrouded(loc) )
301  return 10000;
302 
303  return 1;
304  }
305 
306  private:
307  const gamemap& map_;
308  const team& viewer_;
309  const bool use_shroud_; // Allows faster checks when shroud is disabled.
310  };
311 }//anonymous namespace
312 
313 
314 /**
315  * Checks to see if a leader at @a leader_loc could recruit somewhere.
316  * This takes into account terrain, shroud (for side @a side), and the presence
317  * of visible units.
318  * The behavior for an invalid @a side is subject to change for future needs.
319  */
320 bool game_state::can_recruit_from(const map_location& leader_loc, int side) const
321 {
322  const gamemap& map = board_.map();
323 
324  if(!map.is_keep(leader_loc)) {
325  return false;
326  }
327 
328  try {
329  return pathfind::find_vacant_tile(leader_loc, pathfind::VACANT_CASTLE, nullptr, &board_.get_team(side))
331  } catch(const std::out_of_range&) {
332  // Invalid side specified.
333  // Currently this cannot happen, but it could conceivably be used in
334  // the future to request that shroud and visibility be ignored. Until
335  // that comes to pass, just return.
336  return false;
337  }
338 }
339 
340 bool game_state::can_recruit_from(const unit& leader) const
341 {
342  return can_recruit_from(leader.get_location(), leader.side());
343 }
344 
345 
346 /**
347  * Checks to see if a leader at @a leader_loc could recruit on @a recruit_loc.
348  * This takes into account terrain, shroud (for side @a side), and whether or
349  * not there is already a visible unit at recruit_loc.
350  * The behavior for an invalid @a side is subject to change for future needs.
351  */
352 bool game_state::can_recruit_on(const map_location& leader_loc, const map_location& recruit_loc, int side) const
353 {
354  const gamemap& map = board_.map();
355 
356  if(!map.is_castle(recruit_loc)) {
357  return false;
358  }
359 
360  if(!map.is_keep(leader_loc)) {
361  return false;
362  }
363 
364  try {
365  const team& view_team = board_.get_team(side);
366 
367  if(view_team.shrouded(recruit_loc)) {
368  return false;
369  }
370 
371  if(board_.has_visible_unit(recruit_loc, view_team)) {
372  return false;
373  }
374 
375  castle_cost_calculator calc(map, view_team);
376 
377  // The limit computed in the third argument is more than enough for
378  // any convex castle on the map. Strictly speaking it could be
379  // reduced to sqrt(map.w()**2 + map.h()**2).
381  pathfind::a_star_search(leader_loc, recruit_loc, map.w() + map.h(), calc, map.w(), map.h());
382 
383  return !rt.steps.empty();
384  } catch(const std::out_of_range&) {
385  // Invalid side specified.
386  // Currently this cannot happen, but it could conceivably be used in
387  // the future to request that shroud and visibility be ignored. Until
388  // that comes to pass, just return.
389  return false;
390  }
391 }
392 
393 bool game_state::can_recruit_on(const unit& leader, const map_location& recruit_loc) const
394 {
395  return can_recruit_on(leader.get_location(), recruit_loc, leader.side());
396 }
397 
399 {
400  unit_map::const_iterator leader = board_.units().find(hex);
401  if ( leader != board_.units().end() ) {
402  return leader->can_recruit() && leader->side() == side && can_recruit_from(*leader);
403  } else {
404  // Look for a leader who can recruit on last_hex.
405  for ( leader = board_.units().begin(); leader != board_.units().end(); ++leader) {
406  if ( leader->can_recruit() && leader->side() == side && can_recruit_on(*leader, hex) ) {
407  return true;
408  }
409  }
410  }
411  // No leader found who can recruit at last_hex.
412  return false;
413 }
414 
416 {
417  return this->events_manager_->wml_menu_items();
418 }
419 
421 {
422  return this->events_manager_->wml_menu_items();
423 }
424 
425 
426 namespace
427 {
428  //not really a 'choice' we just need to make sure to inform the server about this.
429 class add_side_wml_choice : public synced_context::server_choice
430 {
431 public:
432  add_side_wml_choice()
433  {
434  }
435 
436  /** We are in a game with no mp server and need to do this choice locally */
437  virtual config local_choice() const
438  {
439  return config{};
440  }
441 
442  /** The request which is sent to the mp server. */
443  virtual config request() const
444  {
445  return config{};
446  }
447 
448  virtual const char* name() const
449  {
450  return "add_side_wml";
451  }
452 
453 private:
454 };
455 } // end anon namespace
456 
457 
459 {
460  cfg["side"] = board_.teams().size() + 1;
461  //if we want to also allow setting the controller we must update the server code.
462  cfg["controller"] = "null";
463  //TODO: is this it? are there caches which must be cleared?
464  board_.teams().emplace_back();
465  board_.teams().back().build(cfg, board_.map(), cfg["gold"].to_int());
466  config choice = synced_context::ask_server_choice(add_side_wml_choice());
467 }
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:51
virtual const std::vector< std::string > & hidden_label_categories() const override
Definition: game_board.hpp:122
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:85
std::unique_ptr< game_lua_kernel > lua_kernel_
Definition: game_state.hpp:51
virtual const unit_map & units() const override
Definition: game_board.hpp:112
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:121
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:352
#define ERR_NG
Definition: game_state.cpp:46
game_events::wmi_manager & get_wml_menu_items()
Definition: game_state.cpp:415
#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:55
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)
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:102
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:72
team & get_team(int i)
Definition: game_board.hpp:97
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:330
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:320
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:107
bool has_visible_unit(const map_location &loc, const team &team, bool see_all=false) const
Definition: game_board.cpp:197
bool is_ignored(const std::string &nick)
Definition: game.cpp:290
#define LOG_NG
Definition: game_state.cpp:44
Encapsulates the map of the game.
Definition: location.hpp:38
Domain specific events.
Definition: action_wml.cpp:87
unit_iterator find(std::size_t id)
Definition: map.cpp:310
bool shrouded(const map_location &loc) const
Definition: team.cpp:652
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:140
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:178
config & add_child(config_key_type key)
Definition: config.cpp:514
void write_config(config &cfg) const
Definition: game_board.cpp:379
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:398
void set_game_display(game_display *)
Definition: game_state.cpp:246
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:1346
game_board board_
Definition: game_state.hpp:47
map_location pos
Definition: game_state.cpp:137
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:78
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:334
void place_sides_in_preferred_locations(const config &level)
Definition: game_state.cpp:143
game_state(const config &level, play_controller &)
Definition: game_state.cpp:48
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:61
void write(config &cfg) const
Definition: game_state.cpp:251
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:458
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.