The Battle for Wesnoth  1.19.5+dev
cave_map_generator.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.net>
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 /**
17  * @file
18  * Map-generator for caves.
19  */
20 
22 #include "log.hpp"
23 #include "map/map.hpp"
24 #include "pathfind/pathfind.hpp"
26 #include "seed_rng.hpp"
27 
28 static lg::log_domain log_engine("engine");
29 #define LOG_NG LOG_STREAM(info, log_engine)
30 
31 static lg::log_domain log_wml("wml");
32 #define ERR_WML LOG_STREAM(err, log_wml)
33 
35  wall_(t_translation::CAVE_WALL),
36  clear_(t_translation::CAVE),
38  castle_(t_translation::DWARVEN_CASTLE),
40  cfg_(cfg),
41  width_(cfg_["map_width"].to_int(50)),
42  height_(cfg_["map_height"].to_int(50)),
43  village_density_(cfg_["village_density"].to_int(0)),
44  flipx_chance_(cfg_["flipx_chance"].to_int()),
45  flipy_chance_(cfg_["flipy_chance"].to_int())
46 {
47 }
48 
50 {
51  return "";
52 }
53 
55 {
56  if(flipx_) {
57  x = params.width_ - x - 1;
58  }
59 
60  return x;
61 }
62 
64 {
65  if(flipy_) {
66  y = params.height_ - y - 1;
67  }
68 
69  return y;
70 }
71 
72 std::string cave_map_generator::create_map(utils::optional<uint32_t> randomseed)
73 {
74  const config res = create_scenario(randomseed);
75  return res["map_data"];
76 }
77 
78 config cave_map_generator::create_scenario(utils::optional<uint32_t> randomseed)
79 {
80  cave_map_generator_job job(*this, randomseed);
81  return job.res_;
82 }
83 
85  : params(pparams)
86  , flipx_(false)
87  , flipy_(false)
88  , map_(t_translation::ter_map(params.width_ + 2 * gamemap::default_border, params.height_ + 2 * gamemap::default_border/*, params.wall_*/))
89  , starting_positions_()
90  , chamber_ids_()
91  , chambers_()
92  , passages_()
93  , res_(params.cfg_.child_or_empty("settings"))
94  , rng_() //initialises with rand()
95 {
96  res_.add_child("event", config {
97  "name", "start",
98  "deprecated_message", config {
99  "what", "scenario_generation=cave",
100  "level", 1,
101  "message", "Use the Lua cave generator instead, with scenario_generation=lua and create_scenario= (see wiki for details).",
102  },
103  });
104  uint32_t seed = randomseed ? *randomseed : seed_rng::next_seed();
105  rng_.seed(seed);
106  LOG_NG << "creating random cave with seed: " << seed;
107  flipx_ = static_cast<int>(rng_() % 100) < params.flipx_chance_;
108  flipy_ = static_cast<int>(rng_() % 100) < params.flipy_chance_;
109 
110  LOG_NG << "creating scenario....";
112 
113  LOG_NG << "placing chambers...";
114  for(std::vector<chamber>::const_iterator c = chambers_.begin(); c != chambers_.end(); ++c) {
115  place_chamber(*c);
116  }
117 
118  LOG_NG << "placing passages...";
119 
120  for(std::vector<passage>::const_iterator p = passages_.begin(); p != passages_.end(); ++p) {
121  place_passage(*p);
122  }
123  LOG_NG << "outputting map....";
124 
126 }
127 
128 void cave_map_generator::cave_map_generator_job::build_chamber(map_location loc, std::set<map_location>& locs, std::size_t size, std::size_t jagged)
129 {
130  if(size == 0 || locs.count(loc) != 0 || !params.on_board(loc))
131  return;
132 
133  locs.insert(loc);
134 
135  for(const map_location& adj : get_adjacent_tiles(loc)) {
136  if(static_cast<int>(rng_() % 100) < (100l - static_cast<long>(jagged))) {
137  build_chamber(adj,locs,size-1,jagged);
138  }
139  }
140 }
141 
143 {
144  for (const config &ch : params.cfg_.child_range("chamber"))
145  {
146  // If there is only a chance of the chamber appearing, deal with that here.
147  if (ch.has_attribute("chance") && static_cast<int>(rng_() % 100) < ch["chance"].to_int()) {
148  continue;
149  }
150 
151  const std::string &xpos = ch["x"];
152  const std::string &ypos = ch["y"];
153 
154  std::size_t min_xpos = 0, min_ypos = 0, max_xpos = params.width_, max_ypos = params.height_;
155 
156  if (!xpos.empty()) {
157  const std::vector<std::string>& items = utils::split(xpos, '-');
158  if(items.empty() == false) {
159  try {
160  min_xpos = std::stoi(items.front()) - 1;
161  max_xpos = std::stoi(items.back());
162  } catch(const std::invalid_argument&) {
163  lg::log_to_chat() << "Invalid min/max coordinates in cave_map_generator: " << items.front() << ", " << items.back() << "\n";
164  ERR_WML << "Invalid min/max coordinates in cave_map_generator: " << items.front() << ", " << items.back();
165  continue;
166  }
167  }
168  }
169 
170  if (!ypos.empty()) {
171  const std::vector<std::string>& items = utils::split(ypos, '-');
172  if(items.empty() == false) {
173  try {
174  min_ypos = std::stoi(items.front()) - 1;
175  max_ypos = std::stoi(items.back());
176  } catch(const std::invalid_argument&) {
177  lg::log_to_chat() << "Invalid min/max coordinates in cave_map_generator: " << items.front() << ", " << items.back() << "\n";
178  ERR_WML << "Invalid min/max coordinates in cave_map_generator: " << items.front() << ", " << items.back();
179  }
180  }
181  }
182  const std::size_t x = translate_x(min_xpos + (rng_()%(max_xpos-min_xpos)));
183  const std::size_t y = translate_y(min_ypos + (rng_()%(max_ypos-min_ypos)));
184 
185  int chamber_size = ch["size"].to_int(3);
186  int jagged_edges = ch["jagged"].to_int();
187 
188  chamber new_chamber;
189  new_chamber.center = map_location(x,y);
190  build_chamber(new_chamber.center,new_chamber.locs,chamber_size,jagged_edges);
191 
192  auto items = ch.optional_child("items");
193  new_chamber.items = items ? &*items : nullptr;
194 
195  const std::string &id = ch["id"];
196  if (!id.empty()) {
197  chamber_ids_[id] = chambers_.size();
198  }
199 
200  chambers_.push_back(new_chamber);
201 
202  for(const config &p : ch.child_range("passage"))
203  {
204  const std::string &dst = p["destination"];
205 
206  // Find the destination of this passage
207  const std::map<std::string,std::size_t>::const_iterator itor = chamber_ids_.find(dst);
208  if(itor == chamber_ids_.end())
209  continue;
210 
211  assert(itor->second < chambers_.size());
212 
213  passages_.emplace_back(new_chamber.center, chambers_[itor->second].center, p);
214  }
215  }
216 }
217 
219 {
220  for(std::set<map_location>::const_iterator i = c.locs.begin(); i != c.locs.end(); ++i) {
221  set_terrain(*i,params.clear_);
222  }
223 
224  if (c.items == nullptr || c.locs.empty()) return;
225 
226  std::size_t index = 0;
227  for(const auto [child_key, child_cfg] : c.items->all_children_view())
228  {
229  config cfg = child_cfg;
230  auto filter = cfg.optional_child("filter");
231  config* object_filter = nullptr;
232  if (auto object = cfg.optional_child("object")) {
233  if (auto of = object->optional_child("filter")) {
234  object_filter = &*of;
235  }
236  }
237 
238  if (!child_cfg["same_location_as_previous"].to_bool()) {
239  index = rng_()%c.locs.size();
240  }
241  std::string loc_var = child_cfg["store_location_as"];
242 
243  std::set<map_location>::const_iterator loc = c.locs.begin();
244  std::advance(loc,index);
245 
246  cfg["x"] = loc->x + 1;
247  cfg["y"] = loc->y + 1;
248 
249  if (filter) {
250  filter["x"] = loc->x + 1;
251  filter["y"] = loc->y + 1;
252  }
253 
254  if (object_filter) {
255  (*object_filter)["x"] = loc->x + 1;
256  (*object_filter)["y"] = loc->y + 1;
257  }
258 
259  // If this is a side, place a castle for the side
260  if (child_key == "side" && !child_cfg["no_castle"].to_bool()) {
261  place_castle(child_cfg["side"].to_int(-1), *loc);
262  }
263 
264  res_.add_child(child_key, cfg);
265 
266  if(!loc_var.empty()) {
267  config &temp = res_.add_child("event");
268  temp["name"] = "prestart";
269  config &xcfg = temp.add_child("set_variable");
270  xcfg["name"] = loc_var + "_x";
271  xcfg["value"] = loc->x + 1;
272  config &ycfg = temp.add_child("set_variable");
273  ycfg["name"] = loc_var + "_y";
274  ycfg["value"] = loc->y + 1;
275  }
276  }
277 }
278 
280 {
282  const t_translation::terrain_code & wall,
283  double laziness, std::size_t windiness,
284  std::mt19937& rng) :
285  map_(mapdata), wall_(wall), laziness_(laziness), windiness_(windiness), rng_(rng)
286  {}
287 
288  virtual double cost(const map_location& loc, const double so_far) const;
289 private:
292  double laziness_;
293  std::size_t windiness_;
294  std::mt19937& rng_;
295 };
296 
297 double passage_path_calculator::cost(const map_location& loc, const double) const
298 {
299  double res = 1.0;
300  if (map_.get(loc.x + gamemap::default_border, loc.y + gamemap::default_border) == wall_) {
301  res = laziness_;
302  }
303 
304  if(windiness_ > 1) {
305  res *= static_cast<double>(rng_()%windiness_);
306  }
307 
308  return res;
309 }
310 
312 {
313  const std::string& chance = p.cfg["chance"];
314  if(!chance.empty() && static_cast<int>(rng_()%100) < std::stoi(chance)) {
315  return;
316  }
317 
318  int windiness = p.cfg["windiness"].to_int();
319  double laziness = std::max<double>(1.0, p.cfg["laziness"].to_double());
320 
321  passage_path_calculator calc(map_, params.wall_, laziness, windiness, rng_);
322 
323  pathfind::plain_route rt = a_star_search(p.src, p.dst, 10000.0, calc, params.width_, params.height_);
324 
325  int width = std::max<int>(1, p.cfg["width"].to_int());
326  int jagged = p.cfg["jagged"].to_int();
327 
328  for(std::vector<map_location>::const_iterator i = rt.steps.begin(); i != rt.steps.end(); ++i) {
329  std::set<map_location> locs;
330  build_chamber(*i,locs,width,jagged);
331  for(std::set<map_location>::const_iterator j = locs.begin(); j != locs.end(); ++j) {
332  set_terrain(*j, params.clear_);
333  }
334  }
335 }
336 
338 {
339  if (params.on_board(loc)) {
341 
342  if(c == params.clear_ || c == params.wall_ || c == params.village_) {
343  // Change this terrain.
344  if ( t == params.clear_ && static_cast<int>(rng_() % 1000) < params.village_density_ )
345  // Override with a village.
346  c = params.village_;
347  else
348  c = t;
349  }
350  }
351 }
352 
354 {
355  if (starting_position != -1) {
356  set_terrain(loc, params.keep_);
357 
360  , loc.y + gamemap::default_border);
361  starting_positions_.insert(t_translation::starting_positions::value_type(std::to_string(starting_position), coord));
362  }
363 
364  for(const map_location& adj : get_adjacent_tiles(loc)) {
365  set_terrain(adj, params.castle_);
366  }
367 }
double t
Definition: astarsearch.cpp:63
static lg::log_domain log_engine("engine")
#define ERR_WML
static lg::log_domain log_wml("wml")
#define LOG_NG
cave_map_generator(const config &game_config)
std::string create_map(utils::optional< uint32_t > randomseed={})
Creates a new map and returns it.
config create_scenario(utils::optional< uint32_t > randomseed={})
std::string config_name() const
Return a friendly name for the generator used to differentiate between different configs of the same ...
t_translation::terrain_code wall_
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
child_itors child_range(config_key_type key)
Definition: config.cpp:272
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:384
config & add_child(config_key_type key)
Definition: config.cpp:440
static const int default_border
The default border style for a map.
Definition: map.hpp:37
Encapsulates the map of the game.
Definition: map.hpp:172
std::size_t i
Definition: function.cpp:1023
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:198
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:479
Standard logging facilities (interface).
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:552
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)
uint32_t next_seed()
Definition: seed_rng.cpp:32
const terrain_code CAVE_WALL
std::string write_game_map(const ter_map &map, const starting_positions &starting_positions, coordinate border_offset)
Write a gamemap in to a vector string.
const terrain_code DWARVEN_KEEP
const terrain_code UNDERGROUND_VILLAGE
const terrain_code DWARVEN_CASTLE
const terrain_code CAVE
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::vector< std::string > split(const config_attribute_value &val)
This module contains various pathfinding functions and utilities.
rect dst
Location on the final composed sheet.
void set_terrain(map_location loc, const t_translation::terrain_code &t)
cave_map_generator_job(const cave_map_generator &params, utils::optional< uint32_t > randomseed={})
void build_chamber(map_location loc, std::set< map_location > &locs, std::size_t size, std::size_t jagged)
std::size_t translate_x(std::size_t x) const
t_translation::starting_positions starting_positions_
std::size_t translate_y(std::size_t y) const
void place_castle(int starting_position, const map_location &loc)
Encapsulates the map of the game.
Definition: location.hpp:45
const t_translation::ter_map & map_
virtual double cost(const map_location &loc, const double so_far) const
t_translation::terrain_code wall_
passage_path_calculator(const t_translation::ter_map &mapdata, const t_translation::terrain_code &wall, double laziness, std::size_t windiness, std::mt19937 &rng)
Structure which holds a single route between one location and another.
Definition: pathfind.hpp:133
std::vector< map_location > steps
Definition: pathfind.hpp:135
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
mock_char c
mock_party p