Merge pull request #324 from cbeck88/lua_map_generator

Lua map generator
This commit is contained in:
Chris Beck 2014-11-05 20:23:15 -05:00
commit fb793a10ce
17 changed files with 485 additions and 54 deletions

View file

@ -5,7 +5,7 @@
id=multiplayer_Random_Map
name= _ "Random map"
description= _ "Randomly generated map. Note: random maps are often unbalanced, but if you have time, you can regenerate them until you get a good one."
map_generation=default
scenario_generation=default
[generator]
[scenario]
name= _ "Random map"

View file

@ -4,7 +4,7 @@
id=multiplayer_Random_Map_Desert
name= _ "Random map (Desert)"
description= _ "A random map with sand as the primary terrain. Note: random maps are often unbalanced, but if you have time, you can regenerate them until you get a good one."
map_generation=default
scenario_generation=default
[generator]
[scenario]
name= _ "Random map (Desert)"

View file

@ -4,7 +4,7 @@
id=multiplayer_Random_Map_Marsh
name= _ "Random map (Marsh)"
description= _ "A random map with swamp as the primary terrain. Note: random maps are often unbalanced, but if you have time, you can regenerate them until you get a good one."
map_generation=default
scenario_generation=default
[generator]
[scenario]
name= _ "Random map (Marsh)"

View file

@ -4,7 +4,7 @@
id=multiplayer_Random_Map_Winter
name= _ "Random map (Winter)"
description= _ "A random map set in the break between spring and winter, mainly with snowy terrains. Note: random maps are often unbalanced, but if you have time, you can regenerate them until you get a good one."
map_generation=default
scenario_generation=default
[generator]
[scenario]
name= _ "Random map (Winter)"

View file

@ -7,24 +7,6 @@
description= _ "Randomly generated map. Note: random maps are often unbalanced, but if you have time, you can regenerate them until you get a good one."
map_generation=yamg
[generator]
[scenario]
name= _ "Random map"
id=multiplayer_Random_Map
{DEFAULT_MUSIC_PLAYLIST}
{DEFAULT_SCHEDULE}
[event]
name=prestart
{SCATTER_EMBELLISHMENTS G* ^Efm 7}
{SCATTER_EMBELLISHMENTS Re,Rb,Gd ^Gvs 5}
{SCATTER_EMBELLISHMENTS G*,D*,R*,Uu*,Ur* ^Es 2}
{SCATTER_EMBELLISHMENTS G*,R*,Uu*,Ur* ^Em 2}
{SCATTER_EMBELLISHMENTS Uu* ^Emf 2}
{SCATTER_EMBELLISHMENTS D* ^Edp 2}
{SCATTER_EMBELLISHMENTS G*,R* ^Wm 0.25}
[/event]
[/scenario]
name=default
map_width=40
map_height=40
@ -256,4 +238,18 @@
[/village_naming]
[/generator]
#undef MIN_COST_ROAD
{DEFAULT_MUSIC_PLAYLIST}
{DEFAULT_SCHEDULE}
[event]
name=prestart
{SCATTER_EMBELLISHMENTS G* ^Efm 7}
{SCATTER_EMBELLISHMENTS Re,Rb,Gd ^Gvs 5}
{SCATTER_EMBELLISHMENTS G*,D*,R*,Uu*,Ur* ^Es 2}
{SCATTER_EMBELLISHMENTS G*,R*,Uu*,Ur* ^Em 2}
{SCATTER_EMBELLISHMENTS Uu* ^Emf 2}
{SCATTER_EMBELLISHMENTS D* ^Edp 2}
{SCATTER_EMBELLISHMENTS G*,R* ^Wm 0.25}
[/event]
[/multiplayer]

View file

@ -1006,6 +1006,7 @@ set(libwesnoth-game_STAT_SRC
generators/map_create.cpp
generators/map_generator.cpp
generators/default_map_generator.cpp
generators/lua_map_generator.cpp
generators/yamg/ya_mapgen.cpp
generators/yamg/yamg_hex.cpp
generators/yamg/yamg_hexheap.cpp

View file

@ -93,6 +93,7 @@ libwesnoth_sources = Split("""
generators/map_create.cpp
generators/map_generator.cpp
generators/default_map_generator.cpp
generators/lua_map_generator.cpp
generators/yamg/ya_mapgen.cpp
generators/yamg/yamg_hex.cpp
generators/yamg/yamg_hexheap.cpp

View file

@ -6,15 +6,27 @@
#include <boost/foreach.hpp>
#include <cassert>
#include <sstream>
namespace ng {
static const config dummy;
configure_engine::configure_engine(saved_game& state) :
state_(state),
parameters_(state_.mp_settings()),
sides_(state_.get_starting_pos().child_range("side")),
cfg_((assert(sides_.first != sides_.second), *sides_.first))
cfg_(sides_.first != sides_.second ? *sides_.first : dummy) //second part is just any old config, it will be ignored
{
if (sides_.first == sides_.second) {
std::stringstream msg;
msg << "Configure Engine: No sides found in scenario, aborting.";
std::cerr << msg;
std::cerr << "Full scenario config:\n";
std::cerr << state_.to_config().debug();
throw game::error(msg.str());
}
set_use_map_settings(use_map_settings_default());
BOOST_FOREACH(const config& scenario,

View file

@ -42,6 +42,7 @@ static lg::log_domain log_config("config");
#define ERR_CF LOG_STREAM(err, log_config)
static lg::log_domain log_mp_create_engine("mp/create/engine");
#define WRN_MP LOG_STREAM(warn, log_mp_create_engine)
#define DBG_MP LOG_STREAM(debug, log_mp_create_engine)
namespace {
@ -251,10 +252,27 @@ std::string user_map::id() const
return name_;
}
random_map::random_map(const config& generator_data) :
scenario(config()),
generator_data_(generator_data)
random_map::random_map(const config& data) :
scenario(data),
generator_data_(),
generate_whole_scenario_(data_.has_attribute("scenario_generation")),
generator_name_(generate_whole_scenario_ ? data_["scenario_generation"] : data_["map_generation"])
{
if (!data.has_child("generator")) {
data_ = config();
generator_data_= config();
data_["description"] = "Error: Random map found with missing generator information. Scenario should have a [generator] child.";
data_["error_message"] = "missing [generator] tag";
} else {
generator_data_ = data.child("generator");
}
if (!data.has_attribute("scenario_generation") && !data.has_attribute("map_generation")) {
data_ = config();
generator_data_= config();
data_["description"] = "Error: Random map found with missing generator information. Scenario should have a [generator] child.";
data_["error_message"] = "couldn't find 'scenario_generation' or 'map_generation' attribute";
}
}
random_map::~random_map()
@ -268,17 +286,32 @@ const config& random_map::generator_data() const
std::string random_map::name() const
{
return generator_data_["name"];
return data_["name"];
}
std::string random_map::description() const
{
return generator_data_["description"];
return data_["description"];
}
std::string random_map::id() const
{
return generator_data_["id"];
return data_["id"];
}
bool random_map::generate_whole_scenario() const
{
return generate_whole_scenario_;
}
std::string random_map::generator_name() const
{
return generator_name_;
}
map_generator * random_map::create_map_generator() const
{
return ::create_map_generator(generator_name(), generator_data());
}
campaign::campaign(const config& data) :
@ -427,16 +460,55 @@ void create_engine::init_generated_level_data()
{
DBG_MP << "initializing generated level data\n";
config data = generator_->create_scenario();
//DBG_MP << "current data:\n";
//DBG_MP << current_level().data().debug();
// Set the scenario to have placing of sides
// based on the terrain they prefer
data["modify_placing"] = "true";
random_map * cur_lev = dynamic_cast<random_map *> (&current_level());
const std::string& description = current_level().data()["description"];
data["description"] = description;
if (!cur_lev) {
WRN_MP << "Tried to initialized generated level data on a level that wasn't a random map\n";
return;
}
try {
if (!cur_lev->generate_whole_scenario())
{
DBG_MP << "** replacing map ** \n";
config data = cur_lev->data();
data["map_data"] = generator_->create_map();
cur_lev->set_data(data);
} else { //scenario generation
DBG_MP << "** replacing scenario ** \n";
config data = generator_->create_scenario();
// Set the scenario to have placing of sides
// based on the terrain they prefer
if (!data.has_attribute("modify_placing")) {
data["modify_placing"] = "true";
}
const std::string& description = cur_lev->data()["description"];
data["description"] = description;
cur_lev->set_data(data);
}
} catch (mapgen_exception & e) {
config data = cur_lev->data();
data["error_message"] = e.what();
cur_lev->set_data(data);
}
//DBG_MP << "final data:\n";
//DBG_MP << current_level().data().debug();
current_level().set_data(data);
}
void create_engine::prepare_for_new_level()
@ -579,6 +651,7 @@ void create_engine::prepare_for_saved_game()
void create_engine::prepare_for_other()
{
DBG_MP << "prepare_for_other\n";
state_.set_scenario(current_level().data());
state_.mp_settings().hash = current_level().data().hash();
}
@ -735,9 +808,7 @@ void create_engine::set_current_level(const size_t index)
random_map* current_random_map =
dynamic_cast<random_map*>(&current_level());
generator_.reset(create_map_generator(
current_random_map->generator_data()["map_generation"],
current_random_map->generator_data().child("generator")));
generator_.reset(current_random_map->create_map_generator());
} else {
generator_.reset(NULL);
}
@ -998,7 +1069,7 @@ void create_engine::init_all_levels()
if (!data["allow_new_game"].to_bool(true))
continue;
if (!data["map_generation"].empty()) {
if (data.has_attribute("map_generation") || data.has_attribute("scenario_generation")) {
random_map_ptr new_random_map(new random_map(data));
random_maps_.push_back(new_random_map);
random_maps_.back()->set_metadata();

View file

@ -108,7 +108,7 @@ private:
class random_map : public scenario
{
public:
random_map(const config& generator_data);
random_map(const config& data);
virtual ~random_map();
const config& generator_data() const;
@ -116,12 +116,20 @@ public:
std::string name() const;
std::string description() const;
std::string id() const;
std::string generator_name() const;
map_generator * create_map_generator() const;
bool generate_whole_scenario() const;
private:
random_map(const random_map&);
void operator=(const random_map&);
config generator_data_;
bool generate_whole_scenario_;
std::string generator_name_;
};
class campaign : public level

View file

@ -594,11 +594,16 @@ static bool enter_configure_mode(game_display& disp, const config& game_config,
mp::ui::result res;
{
mp::configure ui(disp, game_config, gamechat, gamelist, state,
local_players_only);
run_lobby_loop(disp, ui);
res = ui.get_result();
ui.get_parameters();
if (!state.get_starting_pos().child("side")) {
gui2::show_error_message(disp.video(), "No sides found", "This map doesn't have any sides, you can't configure it, skipping...");
res = mp::ui::CREATE;
} else {
mp::configure ui(disp, game_config, gamechat, gamelist, state,
local_players_only);
run_lobby_loop(disp, ui);
res = ui.get_result();
ui.get_parameters();
}
}
switch (res) {

View file

@ -0,0 +1,272 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "lua_map_generator.hpp"
#include "config.hpp"
#include "log.hpp"
#include "lua/lauxlib.h"
#include "lua/lua.h"
#include "lua/lualib.h"
#include "mt_rng.hpp"
#ifdef DEBUG_LUA
#include "scripting/debug_lua.hpp"
#endif
#include "scripting/lua_api.hpp"
#include <string>
#include <boost/foreach.hpp>
static lg::log_domain log_mapgen("mapgen");
#define ERR_NG LOG_STREAM(err, log_mapgen)
#define LOG_NG LOG_STREAM(info, log_mapgen)
#define DBG_NG LOG_STREAM(debug, log_mapgen)
// Add compiler directive suppressing unused variable warning
#if defined(__GNUC__) || defined(__clang__) || defined(__MINGW32__)
#define ATTR_UNUSED( x ) __attribute__((unused)) x
#else
#define ATTR_UNUSED( x ) x
#endif
// Begin lua rng bindings
using rand_rng::mt_rng;
static const char * Rng = "Rng";
static int impl_rng_create(lua_State* L);
static int impl_rng_destroy(lua_State* L);
static int impl_rng_seed(lua_State* L);
static int impl_rng_draw(lua_State* L);
static void initialize_lua_state(lua_State * L)
{
// Open safe libraries.
// Debug and OS are not, but most of their functions will be disabled below.
static const luaL_Reg safe_libs[] = {
{ "", luaopen_base },
{ "table", luaopen_table },
{ "string", luaopen_string },
{ "math", luaopen_math },
{ "debug", luaopen_debug },
{ "os", luaopen_os },
{ NULL, NULL }
};
for (luaL_Reg const *lib = safe_libs; lib->func; ++lib)
{
luaL_requiref(L, lib->name, lib->func, 1);
lua_pop(L, 1); /* remove lib */
}
// Disable functions from os which we don't want.
lua_getglobal(L, "os");
lua_pushnil(L);
while(lua_next(L, -2) != 0) {
lua_pop(L, 1);
char const* function = lua_tostring(L, -1);
if(strcmp(function, "clock") == 0 || strcmp(function, "date") == 0
|| strcmp(function, "time") == 0 || strcmp(function, "difftime") == 0) continue;
lua_pushnil(L);
lua_setfield(L, -3, function);
}
lua_pop(L, 1);
// Disable functions from debug which we don't want.
lua_getglobal(L, "debug");
lua_pushnil(L);
while(lua_next(L, -2) != 0) {
lua_pop(L, 1);
char const* function = lua_tostring(L, -1);
if(strcmp(function, "traceback") == 0) continue;
lua_pushnil(L);
lua_setfield(L, -3, function);
}
lua_pop(L, 1);
lua_settop(L, 0);
// Add mersenne twister rng wrapper
luaL_newmetatable(L, Rng);
static luaL_Reg const callbacks[] = {
{ "create", &impl_rng_create},
{ "__gc", &impl_rng_destroy},
{ "seed", &impl_rng_seed},
{ "draw", &impl_rng_draw},
{ NULL, NULL }
};
luaL_setfuncs(L, callbacks, 0);
lua_pushvalue(L, -1); //make a copy of this table, set it to be its own __index table
lua_setfield(L, -2, "__index");
lua_setglobal(L, Rng);
}
int impl_rng_create(lua_State* L)
{
mt_rng * ATTR_UNUSED(rng) = new ( lua_newuserdata(L, sizeof(mt_rng)) ) mt_rng();
luaL_setmetatable(L, Rng);
return 1;
}
int impl_rng_destroy(lua_State* L)
{
mt_rng * d = static_cast< mt_rng *> (luaL_testudata(L, 1, Rng));
if (d == NULL) {
ERR_NG << "rng_destroy called on data of type: " << lua_typename( L, lua_type( L, 1 ) ) << std::endl;
ERR_NG << "This may indicate a memory leak, please report at bugs.wesnoth.org" << std::endl;
} else {
d->~mt_rng();
}
return 0;
}
int impl_rng_seed(lua_State* L)
{
mt_rng * rng = static_cast<mt_rng *>(luaL_checkudata(L, 1, Rng));
std::string seed = luaL_checkstring(L, 2);
rng->seed_random(seed);
return 0;
}
int impl_rng_draw(lua_State* L)
{
mt_rng * rng = static_cast<mt_rng *>(luaL_checkudata(L, 1, Rng));
lua_pushnumber(L, rng->get_next_random());
return 1;
}
// End Lua Rng bindings
lua_map_generator::lua_map_generator(const config & cfg)
: id_(cfg["id"])
, config_name_(cfg["config_name"])
, create_map_(cfg["create_map"])
, create_scenario_(cfg["create_scenario"])
, mState_(luaL_newstate())
, generator_data_(cfg)
{
const char* required[] = {"id", "config_name", "create_map"};
BOOST_FOREACH(std::string req, required) {
if (!cfg.has_attribute(req)) {
std::string msg = "Error when constructing a lua map generator -- missing a required attribute '";
msg += req + "'\n";
msg += "Config was '" + cfg.debug() + "'";
throw mapgen_exception(msg);
}
}
initialize_lua_state(mState_);
}
lua_map_generator::~lua_map_generator()
{
lua_close(mState_);
}
std::string lua_map_generator::create_map()
{
{
int errcode = luaL_loadstring(mState_, create_map_.c_str());
if (errcode != LUA_OK) {
std::string msg = "Error when running lua_map_generator create_map.\n";
msg += "The generator was: " + config_name_ + "\n";
msg += "Error when parsing create_map function. ";
if (errcode == LUA_ERRSYNTAX) {
msg += "There was a syntax error:\n";
} else {
msg += "There was a memory error:\n";
}
msg += lua_tostring(mState_, -1);
throw mapgen_exception(msg);
}
}
{
luaW_pushconfig(mState_, generator_data_);
int errcode = lua_pcall(mState_, 1, 1, 0);
if (errcode != LUA_OK) {
std::string msg = "Error when running lua_map_generator create_map.\n";
msg += "The generator was: " + config_name_ + "\n";
msg += "Error when running create_map function. ";
if (errcode == LUA_ERRRUN) {
msg += "There was a runtime error:\n";
} else if (errcode == LUA_ERRERR) {
msg += "There was an error with the attached debugger:\n";
} else {
msg += "There was a memory or garbage collection error:\n";
}
msg += lua_tostring(mState_, -1);
throw mapgen_exception(msg);
}
}
if (!lua_isstring(mState_,-1)) {
std::string msg = "Error when running lua_map_generator create_map.\n";
msg += "The generator was: " + config_name_ + "\n";
msg += "create_map did not return a string, instead it returned '";
msg += lua_typename(mState_, lua_type(mState_, -1));
msg += "'";
throw mapgen_exception(msg);
}
return lua_tostring(mState_, -1);
}
config lua_map_generator::create_scenario()
{
if (!create_scenario_.size()) {
return map_generator::create_scenario();
}
{
int errcode = luaL_loadstring(mState_, create_scenario_.c_str());
if (errcode != LUA_OK) {
std::string msg = "Error when running lua_map_generator create_scenario.\n";
msg += "The generator was: " + config_name_ + "\n";
msg += "Error when parsing create_scenario function. ";
if (errcode == LUA_ERRSYNTAX) {
msg += "There was a syntax error:\n";
} else {
msg += "There was a memory error:\n";
}
msg += lua_tostring(mState_, -1);
throw mapgen_exception(msg);
}
}
{
luaW_pushconfig(mState_, generator_data_);
int errcode = lua_pcall(mState_, 1, 1, 0);
if (errcode != LUA_OK) {
std::string msg = "Error when running lua_map_generator create_scenario.\n";
msg += "The generator was: " + config_name_ + "\n";
msg += "Error when running create_scenario function. ";
if (errcode == LUA_ERRRUN) {
msg += "There was a runtime error:\n";
} else if (errcode == LUA_ERRERR) {
msg += "There was an error with the attached debugger:\n";
} else {
msg += "There was a memory or garbage collection error:\n";
}
msg += lua_tostring(mState_, -1);
throw mapgen_exception(msg);
}
}
return luaW_checkconfig(mState_, -1);
}

View file

@ -0,0 +1,57 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef LUA_MAP_GENERATOR_DEFINED
#define LUA_MAP_GENERATOR_DEFINED
#include "config.hpp"
#include "map_generator.hpp"
#include <string>
struct lua_State;
// TODO: Add support for user configurability (via defining a gui2 dialog in lua)
// What's missing is that you need access to the 'wesnoth' object to call show dialog
// at the moment.
class lua_map_generator : public map_generator {
public:
lua_map_generator(const config & cfg);
~lua_map_generator();
bool allow_user_config() const { return false; }
std::string name() const { return "lua"; }
std::string id() const { return id_; }
std::string config_name() const { return config_name_; }
virtual std::string create_map();
virtual config create_scenario();
private:
std::string id_, config_name_;
std::string create_map_;
std::string create_scenario_;
lua_State * mState_;
config generator_data_;
};
#endif

View file

@ -18,11 +18,13 @@
#include "generators/cave_map_generator.hpp"
#include "generators/yamg/ya_mapgen.hpp"
#include "generators/default_map_generator.hpp"
#include "generators/lua_map_generator.hpp"
#include "log.hpp"
#include "scoped_resource.hpp"
#include "serialization/string_utils.hpp"
#include <cassert>
#include <sstream>
static lg::log_domain log_config("config");
#define ERR_CF LOG_STREAM(err, log_config)
@ -35,6 +37,8 @@ map_generator* create_map_generator(const std::string& name, const config &cfg)
return new cave_map_generator(cfg);
} else if(name == "yamg") {
return new ya_mapgen(cfg);
} else if(name == "lua") {
return new lua_map_generator(cfg);
} else {
return NULL;
}
@ -50,8 +54,9 @@ std::string random_generate_map(const std::string& parms, const config &cfg)
assert(!parameters.empty()); //we use parameters.front() in the next line.
util::scoped_ptr<map_generator> generator(create_map_generator(parameters.front(),cfg));
if(generator == NULL) {
ERR_CF << "could not find map generator '" << parameters.front() << "'" << std::endl;
return std::string();
std::stringstream ss;
ss << "could not find map generator '" << parameters.front() << "'";
throw mapgen_exception(ss.str());
}
parameters.erase(parameters.begin());
@ -66,8 +71,9 @@ config random_generate_scenario(const std::string& parms, const config &cfg)
assert(!parameters.empty()); //we use parameters.front() in the next line.
util::scoped_ptr<map_generator> generator(create_map_generator(parameters.front(),cfg));
if(generator == NULL) {
ERR_CF << "could not find map generator '" << parameters.front() << "'" << std::endl;
return config();
std::stringstream ss;
ss << "could not find map generator '" << parameters.front() << "'";
throw mapgen_exception(ss.str());
}
parameters.erase(parameters.begin());

View file

@ -34,9 +34,9 @@
#include <boost/foreach.hpp>
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
static lg::log_domain log_mapgen("mapgen");
#define ERR_NG LOG_STREAM(err, log_mapgen)
#define LOG_NG LOG_STREAM(info, log_mapgen)
config map_generator::create_scenario()
{

View file

@ -820,7 +820,7 @@ struct twrapper<gui2::teditor_generate_map>
std::vector<map_generator*> map_generators;
BOOST_FOREACH(const config &i, main_config.child_range("multiplayer")) {
if(i["map_generation"] == "default") {
if(i["scenario_generation"] == "default") {
const config &generator_cfg = i.child("generator");
if (generator_cfg) {
map_generators.push_back(

View file

@ -86,6 +86,8 @@
#include "SDL_stdinc.h" // for SDL_putenv, Uint32
#include "SDL_timer.h" // for SDL_GetTicks
//#define NO_CATCH_AT_GAME_END
#ifdef _WIN32
#include <windows.h>
#endif