The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
mapgen_lua_kernel.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
17
18#include "config.hpp"
19#include "game_errors.hpp"
20#include "log.hpp"
25#include "deprecation.hpp"
26#include "game_version.hpp"
27
28#include <string>
29#include <functional>
30
33
34static lg::log_domain log_mapgen("mapgen");
35#define ERR_NG LOG_STREAM(err, log_mapgen)
36#define LOG_NG LOG_STREAM(info, log_mapgen)
37#define DBG_NG LOG_STREAM(debug, log_mapgen)
38
39
40
41// Template which allows to push member functions to the lua kernel into lua as C functions, using a shim
42typedef int (mapgen_lua_kernel::*member_callback)(lua_State *);
43
44template <member_callback method>
45int dispatch(lua_State *L) {
46 return ((lua_kernel_base::get_lua_kernel<mapgen_lua_kernel>(L)).*method)(L);
47}
48
49/**
50 * Returns a random number, same interface as math.random.
51 */
52static int intf_random(lua_State *L)
53{
54 std::mt19937& rng = lua_kernel_base::get_lua_kernel<mapgen_lua_kernel>(L).get_default_rng();
55 if(lua_isnoneornil(L, 1)) {
56 double r = double (rng());
57 double r_max = double (rng.max());
58 lua_push(L, r / (r_max + 1));
59 return 1;
60 }
61 else {
62 int32_t min;
63 int32_t max;
64 if(lua_isnumber(L, 2)) {
65 min = lua_check<int32_t>(L, 1);
66 max = lua_check<int32_t>(L, 2);
67 }
68 else {
69 min = 1;
70 max = lua_check<int32_t>(L, 1);
71 }
72 if(min > max) {
73 return luaL_argerror(L, 1, "min > max");
74 }
75 lua_push(L, min + static_cast<int>(rng() % (max - min + 1)));
76 return 1;
77 }
78}
79
80/**
81 * calls the default mapgenerator.
82 */
83static int intf_default_generate(lua_State *L)
84{
85 std::mt19937& rng = lua_kernel_base::get_lua_kernel<mapgen_lua_kernel>(L).get_default_rng();
86
87 int width = luaL_checkinteger(L, 1);
88 int height = luaL_checkinteger(L, 2);
89
91
93 arg.width = width;
94 arg.height = height;
95 arg.nplayers = cfg["nplayers"].to_int(2);
96 arg.nvillages = cfg["nvillages"].to_int(0);
97 arg.iterations = cfg["iterations"].to_int(0);
98 arg.hill_size = cfg["hill_size"].to_int(0);
99 arg.castle_size = cfg["castle_size"].to_int(0);
100 arg.island_size = cfg["island_size"].to_int(0);
101 arg.island_off_center = cfg["island_off_center"].to_int(0);
102 arg.max_lakes = cfg["max_lakes"].to_int(0);
103 arg.link_castles = cfg["link_castles"].to_bool();
104 arg.show_labels = cfg["show_labels"].to_bool(0);
105
106 uint32_t seed = cfg["seed"].to_int(0);
107 if(!cfg.has_attribute("seed")) {
108 seed = rng();
109 }
110
112 std::string res = job.default_generate_map(arg, nullptr, cfg);
113
114 lua_push(L, res);
115 return 1;
116}
117
118/**
119 * calls the default mapgenerator.
120 */
121static int intf_default_generate_height_map(lua_State *L)
122{
123 std::mt19937& rng = lua_kernel_base::get_lua_kernel<mapgen_lua_kernel>(L).get_default_rng();
124
125 int width = luaL_checkinteger(L, 1);
126 int height = luaL_checkinteger(L, 2);
127
129
130 if(!cfg.has_attribute("location_set")) {
131 deprecated_message("generate_height_map(..., {location_set=false})", DEP_LEVEL::PREEMPTIVE, "1.17", "The default value of this option will be changed to true in 1.17.");
132 }
133
134 int iterations = cfg["iterations"].to_int(1);
135 int hill_size = cfg["hill_size"].to_int(1);
136 int island_size = cfg["island_size"].to_int(width/2);
137 int center_x = cfg["center_x"].to_int(width/2);
138 int center_y = cfg["center_y"].to_int(height/2);
139 bool flip_layout = cfg["flip_format"].to_bool();
140 bool as_locset = cfg["location_set"].to_bool(false);
141 uint32_t seed = cfg["seed"].to_int(0);
142
143 if(!cfg.has_attribute("seed")) {
144 seed = rng();
145 }
147 default_map_generator_job::height_map res = job.generate_height_map(width, height, iterations, hill_size, island_size, center_x, center_y);
148 lua_createtable (L, width * height, 0);
149 assert(int(res.size()) == width);
150 assert((width == 0 || int(res[0].size()) == height));
151 std::hash<map_location> loc_hash;
152 for(int x = 0; x != width; ++x) {
153 for(int y = 0; y != height; ++y) {
154 int h = res[x][y];
155 lua_pushinteger (L, h);
156 if(as_locset) {
157 map_location loc(flip_layout ? y : x, flip_layout ? x : y, wml_loc());
158 lua_rawseti(L, -2, loc_hash(loc));
159 } else {
160 int i = flip_layout ? (y + x * height) : (x + y * width);
161 lua_rawseti(L, -2, i);
162 }
163 }
164 }
165 return 1;
166}
167/**
168 * Finds a path between two locations.
169 * - Args 1: source location.
170 * - Args 2: destination.
171 * - Arg 3: cost function
172 * - Args 4,5 size of map.
173 * - Arg 6 include border.
174 * OR
175 * - Arg 3: options table containing calculate, width, height, (optional) include_borders
176 * - Ret 1: array of pairs containing path steps.
177 * - Ret 2: path cost.
178 */
179static int intf_find_path(lua_State *L)
180{
181 int arg = 1;
183 if(lua_isfunction(L, arg)) {
184 const char *msg = lua_pushfstring(L, "%s expected, got %s", lua_typename(L, LUA_TFUNCTION), luaL_typename(L, 3));
185 return luaL_argerror(L, 3, msg);
186 }
187 utils::optional<lua_pathfind_cost_calculator> calc;
188 int width, height;
189 bool border = false;
190 if(lua_istable(L, 3)) {
191 if(luaW_tableget(L, 3, "calculate")) {
192 calc = lua_pathfind_cost_calculator(L, lua_gettop(L));
193 } else {
194 return luaL_argerror(L, 3, "missing key: calculate");
195 }
196 if(!luaW_tableget(L, 3, "width")) {
197 width = luaL_checkinteger(L, -1);
198 } else {
199 return luaL_argerror(L, 3, "missing key: width");
200 }
201 if(!luaW_tableget(L, 3, "height")) {
202 height = luaL_checkinteger(L, -1);
203 } else {
204 return luaL_argerror(L, 3, "missing key: height");
205 }
206 if(!luaW_tableget(L, 3, "include_borders")) {
207 border = luaW_toboolean(L, -1);
208 }
209 } else {
210 calc = lua_pathfind_cost_calculator(L, 3);
211 width = luaL_checkinteger(L, 4);
212 height = luaL_checkinteger(L, 5);
213 if(lua_isboolean(L, 6)) {
214 border = luaW_toboolean(L, 6);
215 }
216 }
217 pathfind::plain_route res = pathfind::a_star_search(src, dst, 10000, *calc, width, height, nullptr, border);
218
219 int nb = res.steps.size();
220 lua_createtable(L, nb, 0);
221 for (int i = 0; i < nb; ++i)
222 {
223 lua_createtable(L, 2, 0);
224 lua_pushinteger(L, res.steps[i].wml_x());
225 lua_rawseti(L, -2, 1);
226 lua_pushinteger(L, res.steps[i].wml_y());
227 lua_rawseti(L, -2, 2);
228 lua_rawseti(L, -2, i + 1);
229 }
230 lua_pushinteger(L, res.move_cost);
231
232 return 2;
233}
234
235
238 , random_seed_()
239 , default_rng_()
240 , vars_(vars)
241{
242 lua_State *L = mState;
243
244 // Overwrite mathx.random. This guarantees that the mapgen_lua_kernel version
245 // of mathx.random overrides the lua_kernel_base version.
246 lua_getglobal(L, "mathx");
247 lua_pushcfunction(L, &intf_random);
248 lua_setfield(L, -2, "random");
249
250 lua_settop(L, 0);
251
252 static luaL_Reg const map_callbacks[] {
253 // Map methods
254 { "find", &intf_mg_get_locations },
255 { "find_in_radius", &intf_mg_get_tiles_radius },
256 // Static functions
257 { "filter", &intf_terrainfilter_create },
258 { "create", &intf_terrainmap_create },
259 { "generate_height_map", &intf_default_generate_height_map },
260 { "generate", &intf_default_generate },
261 { nullptr, nullptr }
262 };
263
264 luaW_getglobal(L, "wesnoth", "map");
265 assert(lua_istable(L,-1));
266 luaL_setfuncs(L, map_callbacks, 0);
267 lua_pop(L, 1);
268 assert(lua_gettop(L) == 0);
269
270 // Create the paths module
271 cmd_log_ << "Adding paths module...\n";
272 static luaL_Reg const path_callbacks[] {
273 { "find_path", &intf_find_path },
274 { nullptr, nullptr }
275 };
276 lua_getglobal(L, "wesnoth");
277 lua_newtable(L);
278 luaL_setfuncs(L, path_callbacks, 0);
279 lua_setfield(L, -2, "paths");
280 lua_pop(L, 1);
281
282 // Add functions to the WML module
283 lua_getglobal(L, "wml");
284 static luaL_Reg const wml_callbacks[] {
285 {"tovconfig", &lua_common::intf_tovconfig},
286 // These aren't actually part of the API - they're used internally by the variable metatable.
287 { "get_variable", &dispatch<&mapgen_lua_kernel::intf_get_variable>},
288 { "get_all_vars", &dispatch<&mapgen_lua_kernel::intf_get_all_vars>},
289 { nullptr, nullptr }
290 };
291 luaL_setfuncs(L, wml_callbacks, 0);
292 lua_pop(L, 1);
293
296}
297
298void mapgen_lua_kernel::run_generator(const char * prog, const config & generator)
299{
300 load_string(prog, "", std::bind(&lua_kernel_base::throw_exception, this, std::placeholders::_1, std::placeholders::_2));
301 luaW_pushconfig(mState, generator);
302 protected_call(1, 1, std::bind(&lua_kernel_base::throw_exception, this, std::placeholders::_1, std::placeholders::_2));
303}
304
305void mapgen_lua_kernel::user_config(const char * prog, const config & generator)
306{
307 run_generator(prog, generator);
308}
309
311{
312 char const *m = luaL_checkstring(L, 1);
313 if(vars_) {
315 return luaW_pushvariable(L, v) ? 1 : 0;
316 }
317 return 0;
318}
319
322 return 1;
323}
324
325std::string mapgen_lua_kernel::create_map(const char * prog, const config & generator, utils::optional<uint32_t> seed) // throws game::lua_error
326{
327 random_seed_ = seed;
328 default_rng_ = std::mt19937(get_random_seed());
329 run_generator(prog, generator);
330
331 if (!lua_isstring(mState,-1)) {
332 std::string msg = "expected a string, found a ";
333 msg += lua_typename(mState, lua_type(mState, -1));
334 lua_pop(mState, 1);
335 throw game::lua_error(msg.c_str(),"bad return value");
336 }
337
338 return lua_tostring(mState, -1);
339}
340
341config mapgen_lua_kernel::create_scenario(const char * prog, const config & generator, utils::optional<uint32_t> seed) // throws game::lua_error
342{
343 random_seed_ = seed;
344 default_rng_ = std::mt19937(get_random_seed());
345 run_generator(prog, generator);
346
347 if (!lua_istable(mState, -1)) {
348 std::string msg = "expected a config (table), found a ";
349 msg += lua_typename(mState, lua_type(mState, -1));
350 lua_pop(mState, 1);
351 throw game::lua_error(msg.c_str(),"bad return value");
352 }
353 config result;
354 if (!luaW_toconfig(mState, -1, result)) {
355 std::string msg = "expected a config, but it is malformed ";
356 lua_pop(mState, 1);
357 throw game::lua_error(msg.c_str(),"bad return value");
358 }
359 return result;
360}
361
363{
364 if(random_seed_) {
365 return (*random_seed_)++;
366 }
367 else {
369 }
370}
371
373{
374 if(!default_rng_) {
375 default_rng_ = std::mt19937(get_random_seed());
376 }
377 return *default_rng_;
378}
map_location loc
Definition move.cpp:172
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
bool has_attribute(std::string_view key) const
Definition config.cpp:157
std::string default_generate_map(generator_data data, std::map< map_location, std::string > *labels, const config &cfg)
Generate the map.
height_map generate_height_map(std::size_t width, std::size_t height, std::size_t iterations, std::size_t hill_size, std::size_t island_size, std::size_t island_off_center)
Generate a height-map.
std::vector< std::vector< int > > height_map
bool protected_call(int nArgs, int nRets, const error_handler &)
bool load_string(const std::string &prog, const std::string &name, const error_handler &, bool allow_unsafe=false)
virtual uint32_t get_random_seed()
virtual void throw_exception(char const *msg, char const *context="Lua error")
int intf_get_variable(lua_State *L)
virtual uint32_t get_random_seed()
void run_generator(const char *prog, const config &generator)
void user_config(const char *prog, const config &generator)
std::string create_map(const char *prog, const config &generator, utils::optional< uint32_t > seed)
config create_scenario(const char *prog, const config &generator, utils::optional< uint32_t > seed)
mapgen_lua_kernel(const config *vars)
std::mt19937 & get_default_rng()
utils::optional< uint32_t > random_seed_
int intf_get_all_vars(lua_State *L)
utils::optional< std::mt19937 > default_rng_
Information on a WML variable.
Definitions for the interface to Wesnoth Markup Language (WML).
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
@ border
The border of the map.
const config * cfg
Interfaces for manipulating version numbers of engine, add-ons, etc.
Standard logging facilities (interface).
void luaW_pushconfig(lua_State *L, const config &cfg)
Converts a config object to a Lua table pushed at the top of the stack.
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
bool luaW_pushvariable(lua_State *L, variable_access_const &v)
bool luaW_toboolean(lua_State *L, int n)
bool luaW_tableget(lua_State *L, int index, const char *key)
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
map_location luaW_checklocation(lua_State *L, int index)
Converts an optional table or pair of integers to a map location object.
bool luaW_getglobal(lua_State *L, const std::vector< std::string > &path)
Pushes the value found by following the variadic names (char *), if the value is not nil.
int intf_mg_get_tiles_radius(lua_State *L)
int intf_terrainfilter_create(lua_State *L)
Create a filter.
int intf_mg_get_locations(lua_State *L)
int intf_terrainmap_create(lua_State *L)
Create a map.
int dispatch(lua_State *L)
static int intf_default_generate_height_map(lua_State *L)
calls the default mapgenerator.
static lg::log_domain log_mapgen("mapgen")
static int intf_find_path(lua_State *L)
Finds a path between two locations.
static int intf_random(lua_State *L)
Returns a random number, same interface as math.random.
int(mapgen_lua_kernel::* member_callback)(lua_State *)
static int intf_default_generate(lua_State *L)
calls the default mapgenerator.
int intf_tovconfig(lua_State *L)
Creates a vconfig containing the WML table.
std::string register_metatables(lua_State *L)
std::string register_metatables(lua_State *L)
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)
void lua_push(lua_State *L, const T &val)
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
Error used to report an error in a lua script or in the lua interpreter.
Cost function object relying on a Lua function.
Encapsulates the map of the game.
Definition location.hpp:46
Structure which holds a single route between one location and another.
Definition pathfind.hpp:133
std::vector< map_location > steps
Definition pathfind.hpp:135
int move_cost
Movement cost for reaching the end of the route.
Definition pathfind.hpp:137
#define i
#define h