The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
game_state.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2014 - 2025
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"
22#include "log.hpp"
23#include "map/map.hpp"
24#include "pathfind/pathfind.hpp"
25#include "pathfind/teleport.hpp"
26#include "play_controller.hpp"
29#include "reports.hpp"
31#include "synced_context.hpp"
32#include "teambuilder.hpp"
33#include "units/unit.hpp"
35#include "side_controller.hpp"
36
37#include <algorithm>
38#include <set>
39
40static lg::log_domain log_engine("engine");
41#define LOG_NG LOG_STREAM(info, log_engine)
42#define DBG_NG LOG_STREAM(debug, log_engine)
43#define ERR_NG LOG_STREAM(err, log_engine)
44
46 : gamedata_(level)
47 , board_(level)
48 , tod_manager_(level)
49 , pathfind_manager_(new pathfind::manager(level))
50 , reports_(new reports())
51 , lua_kernel_(new game_lua_kernel(*this, pc, *reports_))
52 , ai_manager_()
53 , events_manager_(new game_events::manager())
54 , undo_stack_(new actions::undo_list())
55 , player_number_(level["playing_team"].to_int() + 1)
56 , next_player_number_(level["next_player_number"].to_int(player_number_ + 1))
57 , do_healing_(level["do_healing"].to_bool(false))
58 , victory_when_enemies_defeated_(level["victory_when_enemies_defeated"].to_bool(true))
59 , remove_from_carryover_on_defeat_(level["remove_from_carryover_on_defeat"].to_bool(true))
60 , server_request_number_(level["server_request_number"].to_int())
61{
62 lua_kernel_->load_core();
63 if(auto endlevel_cfg = level.optional_child("end_level_data")) {
64 end_level_data el_data;
65 el_data.read(*endlevel_cfg);
66 el_data.transient.carryover_report = false;
67 end_level_data_ = el_data;
68 }
69}
70
72
73static int placing_score(const config& side, const gamemap& map, const map_location& pos)
74{
75 int positions = 0, liked = 0;
76 const t_translation::ter_list terrain = t_translation::read_list(side["terrain_liked"].str());
77
78 for(int i = -8; i != 8; ++i) {
79 for(int j = -8; j != +8; ++j) {
80 const map_location pos2 = pos.plus(i, j);
81 if(map.on_board(pos2)) {
82 ++positions;
83 if(std::count(terrain.begin(),terrain.end(),map[pos2])) {
84 ++liked;
85 }
86 }
87 }
88 }
89
90 return (100*liked)/positions;
91}
92
94
96 side(0),
97 score(0),
98 pos()
99 {
100 }
101
104};
105
106static bool operator<(const placing_info& a, const placing_info& b) { return a.score > b.score; }
107
108
110{
111 std::vector<placing_info> placings;
112
113 int num_pos = board_.map().num_valid_starting_positions();
114
115 int side_num = 1;
116 for(const config &side : level.child_range("side"))
117 {
118 for(int p = 1; p <= num_pos; ++p) {
119 const map_location& pos = board_.map().starting_position(p);
120 int score = placing_score(side, board_.map(), pos);
121 placing_info obj;
122 obj.side = side_num;
123 obj.score = score;
124 obj.pos = pos;
125 placings.push_back(obj);
126 }
127 ++side_num;
128 }
129
130 std::stable_sort(placings.begin(),placings.end());
131 std::set<int> placed;
132 std::set<map_location> positions_taken;
133
134 for (std::vector<placing_info>::const_iterator i = placings.begin(); i != placings.end() && static_cast<int>(placed.size()) != side_num - 1; ++i) {
135 if(placed.count(i->side) == 0 && positions_taken.count(i->pos) == 0) {
136 placed.insert(i->side);
137 positions_taken.insert(i->pos);
138 board_.map().set_starting_position(i->side,i->pos);
139 LOG_NG << "placing side " << i->side << " at " << i->pos;
140 }
141 }
142}
143
145{
146 events_manager_->read_scenario(level, *lua_kernel_);
148 if (level["modify_placing"].to_bool()) {
149 LOG_NG << "modifying placing...";
151 }
152
153 LOG_NG << "initialized time of day regions... " << pc.timer();
154 for (const config &t : level.child_range("time_area")) {
156 }
157
158 LOG_NG << "initialized teams... " << pc.timer();
159
160 board_.teams().resize(level.child_count("side"));
161 if (player_number_ != 1 && player_number_ > static_cast<int>(board_.teams().size())) {
162 ERR_NG << "invalid player number " << player_number_ << " #sides=" << board_.teams().size();
163 player_number_ = 1;
164 // in case there are no teams, using player_number_ migh still cause problems later.
165 }
166
167 std::vector<team_builder> team_builders;
168
169 // Note this isn't strictly necessary since team_builder declares a move constructor which will
170 // be used if a copy is needed (see the class documentation for why a copy causes crashes), but
171 // it can't hurt to be doubly safe.
172 team_builders.reserve(board_.teams().size());
173
174 int team_num = 0;
175 for (const config &side : level.child_range("side"))
176 {
177 ++team_num;
178
179 team_builders.emplace_back(side, board_.get_team(team_num), level, board_, team_num);
180 team_builders.back().build_team_stage_one();
181 }
182
183 //Initialize the lua kernel before the units are created.
184 lua_kernel_->initialize(level);
185
186 {
187 //sync traits of start units and the random start time.
189
191
192 undo_stack_->read(level.child_or_empty("undo_stack"), player_number_);
193
194 for(team_builder& tb : team_builders) {
195 tb.build_team_stage_two();
196 }
197 for(team_builder& tb : team_builders) {
198 tb.build_team_stage_three();
199 }
200
201 for(const team& t : board_.teams()) {
202 // Labels from players in your ignore list default to hidden
203 if(prefs::get().is_ignored(t.current_player())) {
204 std::string label_cat = "side:" + std::to_string(t.side());
205 board_.hidden_label_categories().push_back(label_cat);
206 }
207 }
208 }
209}
210
212{
213 lua_kernel_->set_game_display(gd);
214}
215
217{
218 // dont write this before we fired the (pre)start events
219 // This is the case for the 'replay_start' part of the savegame.
221 cfg["playing_team"] = player_number_ - 1;
222 cfg["next_player_number"] = next_player_number_;
223 }
224 cfg["server_request_number"] = server_request_number_;
225 cfg["do_healing"] = do_healing_;
226 cfg["victory_when_enemies_defeated"] = victory_when_enemies_defeated_;
227 cfg["remove_from_carryover_on_defeat"] = remove_from_carryover_on_defeat_;
228 //Call the lua save_game functions
229 lua_kernel_->save_game(cfg);
230
231 //Write the game events.
232 events_manager_->write_events(cfg);
233
234 //Write the map, unit_map, and teams info
236
237 //Write the tod manager, and time areas
239
240 //write out the current state of the map
241 cfg.merge_with(pathfind_manager_->to_config());
242
243 //Write the game data, including wml vars
245
246 // Preserve the undo stack so that fog/shroud clearing is kept accurate.
247 undo_stack_->write(cfg.add_child("undo_stack"));
248
249 if(end_level_data_) {
250 end_level_data_->write(cfg.add_child("end_level_data"));
251 }
252}
253
254namespace {
255 struct castle_cost_calculator : pathfind::cost_calculator
256 {
257 castle_cost_calculator(const gamemap& map, const team & view_team) :
258 map_(map),
259 viewer_(view_team),
260 use_shroud_(view_team.uses_shroud())
261 {}
262
263 virtual double cost(const map_location& loc, const double) const
264 {
265 if(!map_.is_castle(loc))
266 return 10000;
267
268 if ( use_shroud_ && viewer_.shrouded(loc) )
269 return 10000;
270
271 return 1;
272 }
273
274 private:
275 const gamemap& map_;
276 const team& viewer_;
277 const bool use_shroud_; // Allows faster checks when shroud is disabled.
278 };
279}//anonymous namespace
280
281
282/**
283 * Checks to see if a leader at @a leader_loc could recruit somewhere.
284 * This takes into account terrain, shroud (for side @a side), and the presence
285 * of visible units.
286 * The behavior for an invalid @a side is subject to change for future needs.
287 */
288bool game_state::can_recruit_from(const map_location& leader_loc, int side) const
289{
290 const gamemap& map = board_.map();
291
292 if(!map.is_keep(leader_loc)) {
293 return false;
294 }
295
296 try {
297 return pathfind::find_vacant_tile(leader_loc, pathfind::VACANT_CASTLE, nullptr, &board_.get_team(side))
299 } catch(const std::out_of_range&) {
300 // Invalid side specified.
301 // Currently this cannot happen, but it could conceivably be used in
302 // the future to request that shroud and visibility be ignored. Until
303 // that comes to pass, just return.
304 return false;
305 }
306}
307
308bool game_state::can_recruit_from(const unit& leader) const
309{
310 return can_recruit_from(leader.get_location(), leader.side());
311}
312
313
314/**
315 * Checks to see if a leader at @a leader_loc could recruit on @a recruit_loc.
316 * This takes into account terrain, shroud (for side @a side), and whether or
317 * not there is already a visible unit at recruit_loc.
318 * The behavior for an invalid @a side is subject to change for future needs.
319 */
320bool game_state::can_recruit_on(const map_location& leader_loc, const map_location& recruit_loc, int side) const
321{
322 const gamemap& map = board_.map();
323
324 if(!map.is_castle(recruit_loc)) {
325 return false;
326 }
327
328 if(!map.is_keep(leader_loc)) {
329 return false;
330 }
331
332 try {
333 const team& view_team = board_.get_team(side);
334
335 if(view_team.shrouded(recruit_loc)) {
336 return false;
337 }
338
339 if(board_.has_visible_unit(recruit_loc, view_team)) {
340 return false;
341 }
342
343 castle_cost_calculator calc(map, view_team);
344
345 // The limit computed in the third argument is more than enough for
346 // any convex castle on the map. Strictly speaking it could be
347 // reduced to sqrt(map.w()**2 + map.h()**2).
349 pathfind::a_star_search(leader_loc, recruit_loc, map.w() + map.h(), calc, map.w(), map.h());
350
351 return !rt.steps.empty();
352 } catch(const std::out_of_range&) {
353 // Invalid side specified.
354 // Currently this cannot happen, but it could conceivably be used in
355 // the future to request that shroud and visibility be ignored. Until
356 // that comes to pass, just return.
357 return false;
358 }
359}
360
361bool game_state::can_recruit_on(const unit& leader, const map_location& recruit_loc) const
362{
363 return can_recruit_on(leader.get_location(), recruit_loc, leader.side());
364}
365
367{
369 if ( leader != board_.units().end() ) {
370 return leader->can_recruit() && leader->side() == side && can_recruit_from(*leader);
371 } else {
372 // Look for a leader who can recruit on last_hex.
373 for ( leader = board_.units().begin(); leader != board_.units().end(); ++leader) {
374 if ( leader->can_recruit() && leader->side() == side && can_recruit_on(*leader, hex) ) {
375 return true;
376 }
377 }
378 }
379 // No leader found who can recruit at last_hex.
380 return false;
381}
382
384{
385 return this->events_manager_->wml_menu_items();
386}
387
389{
390 return this->events_manager_->wml_menu_items();
391}
392
394{
395 return !gamedata_.next_scenario().empty() && gamedata_.next_scenario() != "null";
396}
397
398namespace
399{
400 //not really a 'choice' we just need to make sure to inform the server about this.
401class add_side_wml_choice : public synced_context::server_choice
402{
403public:
404 add_side_wml_choice()
405 {
406 }
407
408 /** We are in a game with no mp server and need to do this choice locally */
409 virtual config local_choice() const
410 {
411 return config{};
412 }
413
414 /** The request which is sent to the mp server. */
415 virtual config request() const
416 {
417 return config{};
418 }
419
420 virtual const char* name() const
421 {
422 return "add_side_wml";
423 }
424
425private:
426};
427} // end anon namespace
428
429
431{
432 cfg["side"] = board_.teams().size() + 1;
433 //if we want to also allow setting the controller we must update the server code.
434 cfg["controller"] = side_controller::none;
435 //TODO: is this it? are there caches which must be cleared?
436 board_.teams().emplace_back();
437 board_.teams().back().build(cfg, board_.map());
438 config choice = synced_context::ask_server_choice(add_side_wml_choice());
439}
map_location loc
Definition move.cpp:172
#define ERR_NG
#define LOG_NG
double t
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition config.cpp:1097
virtual const gamemap & map() const override
virtual const std::vector< team > & teams() const override
bool has_visible_unit(const map_location &loc, const team &team, bool see_all=false) const
team & get_team(int i)
void write_config(config &cfg) const
virtual const unit_map & units() const override
virtual const std::vector< std::string > & hidden_label_categories() const override
@ PRELOAD
the preload [event] is fired next phase: PRESTART (normal game), TURN_STARTING_WAITING (reloaded game...
Definition game_data.hpp:76
@ INITIAL
creating intitial [unit]s, executing toplevel [lua] etc.
Definition game_data.hpp:73
void write_snapshot(config &cfg) const
const std::string & next_scenario() const
const randomness::mt_rng & rng() const
Definition game_data.hpp:67
void write(config &cfg) const
utils::optional< end_level_data > end_level_data_
bool remove_from_carryover_on_defeat_
void add_side_wml(config cfg)
creates a new side during a game.
int player_number_
bool has_next_scenario() const
Checks whether this is not the last scenario (usually of a campaign)
int next_player_number_
int server_request_number_
bool can_recruit_from(const map_location &leader_loc, int side) const
Checks to see if a leader at leader_loc could recruit somewhere.
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.
bool side_can_recruit_on(int side, map_location loc) const
Checks if any of the sides leaders can recruit at a location.
bool in_phase(game_data::PHASE phase) const
void set_game_display(game_display *)
std::unique_ptr< game_lua_kernel > lua_kernel_
game_state(const config &level, play_controller &)
std::unique_ptr< pathfind::manager > pathfind_manager_
const std::unique_ptr< actions::undo_list > undo_stack_
undo_stack_ is never nullptr.
void init(const config &level, play_controller &)
bool do_healing_
True if healing should be done at the beginning of the next side turn.
void place_sides_in_preferred_locations(const config &level)
game_board board_
const std::unique_ptr< game_events::manager > events_manager_
game_events::wmi_manager & get_wml_menu_items()
bool victory_when_enemies_defeated_
tod_manager tod_manager_
game_data gamedata_
int w() const
Effective map width.
Definition map.hpp:50
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition map.cpp:342
int h() const
Effective map height.
Definition map.hpp:53
map_location starting_position(int side) const
Definition map.cpp:286
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition map.cpp:297
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition map.cpp:347
Encapsulates the map of the game.
Definition map.hpp:176
bool is_castle(const map_location &loc) const
Definition map.cpp:64
bool is_keep(const map_location &loc) const
Definition map.cpp:66
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
const auto & timer() const
static prefs & get()
bool is_ignored(const std::string &nick)
RAII class to use rng_deterministic in the current scope.
virtual const char * name() const =0
virtual config request() const =0
The request which is sent to the mp server.
virtual config local_choice() const =0
We are in a game with no mp server and need to do this choice locally.
static config ask_server_choice(const server_choice &)
If we are in a mp game, ask the server, otherwise generate the answer ourselves.
This class stores all the data for a single 'side' (in game nomenclature).
Definition team.hpp:74
bool shrouded(const map_location &loc) const
Definition team.cpp:636
config to_config(const std::string &textdomain="") const
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.
void resolve_random(randomness::rng &r)
handles random_start_time, should be called before the game starts.
unit_iterator end()
Definition map.hpp:428
unit_iterator find(std::size_t id)
Definition map.cpp:302
unit_iterator begin()
Definition map.hpp:418
This class represents a single unit of a specific type.
Definition unit.hpp:39
const config * cfg
static bool operator<(const placing_info &a, const placing_info &b)
static int placing_score(const config &side, const gamemap &map, const map_location &pos)
static lg::log_domain log_engine("engine")
int side() const
The side this unit belongs to.
Definition unit.hpp:249
const map_location & get_location() const
The current map location this unit is at.
Definition unit.hpp:1327
Standard logging facilities (interface).
Domain specific events.
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)
@ VACANT_CASTLE
Definition pathfind.hpp:39
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,...
Definition pathfind.cpp:54
rng * generator
This generator is automatically synced during synced context.
Definition random.cpp:60
Unit and team statistics.
std::vector< terrain_code > ter_list
ter_list read_list(std::string_view str, const ter_layer filler)
Reads a list of terrains from a string, when reading the.
This module contains various pathfinding functions and utilities.
Additional information on the game outcome which can be provided by WML.
void read(const config &cfg)
transient_end_level transient
Encapsulates the map of the game.
Definition location.hpp:46
static const map_location & null_location()
Definition location.hpp:103
map_location plus(int x_diff, int y_diff) const
Definition location.hpp:193
virtual double cost(const map_location &loc, const double so_far) const =0
Structure which holds a single route between one location and another.
Definition pathfind.hpp:133
std::vector< map_location > steps
Definition pathfind.hpp:135
map_location pos
bool carryover_report
Should a summary of the scenario outcome be displayed?
mock_party p
Various functions that implement the undoing (and redoing) of in-game commands.
#define i
#define b