The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
cave_map_generator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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
28static lg::log_domain log_engine("engine");
29#define LOG_NG LOG_STREAM(info, log_engine)
30
31static 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),
37 village_(t_translation::UNDERGROUND_VILLAGE),
38 castle_(t_translation::DWARVEN_CASTLE),
39 keep_(t_translation::DWARVEN_KEEP),
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
72std::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
78config 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(const chamber& c : chambers_) {
116 }
117
118 LOG_NG << "placing passages...";
119
120 for(const passage& p : passages_) {
122 }
123 LOG_NG << "outputting map....";
124
126}
127
128void 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(const map_location& loc : c.locs) {
221 set_terrain(loc, 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;
289private:
292 double laziness_;
293 std::size_t windiness_;
294 std::mt19937& rng_;
295};
296
297double passage_path_calculator::cost(const map_location& loc, const double) const
298{
299 double res = 1.0;
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(const map_location& step : rt.steps) {
329 std::set<map_location> locs;
330 build_chamber(step, locs, width, jagged);
331 for(const map_location& loc : locs) {
332 set_terrain(loc, 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
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}
map_location loc
Definition move.cpp:172
#define LOG_NG
double t
static lg::log_domain log_engine("engine")
static lg::log_domain log_wml("wml")
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:157
config & add_child(std::string_view key)
Definition config.cpp:436
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition config.cpp:380
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:176
#define ERR_WML
const config * cfg
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition location.cpp:513
Standard logging facilities (interface).
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition log.cpp:550
uint32_t next_seed()
Definition seed_rng.cpp:32
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.
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:46
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...
mock_party p
#define c