From 75b61e6401379c5d16ed73fa3e6b5b850b85c061 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Tue, 4 Nov 2014 19:54:51 -0500 Subject: [PATCH 01/10] don't crash when attempting to configure a level with no sides A similar assertion failure also exists in mp_create I believe. It's a bit ugly to have to put this in src/game_initialization/ multiplayer.cpp, but probably the best thing if these classes aren't going to be robust against corner cases like this. --- src/game_initialization/configure_engine.cpp | 14 +++++++++++++- src/game_initialization/multiplayer.cpp | 15 ++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/game_initialization/configure_engine.cpp b/src/game_initialization/configure_engine.cpp index 558b95d7853..5a362f7555b 100644 --- a/src/game_initialization/configure_engine.cpp +++ b/src/game_initialization/configure_engine.cpp @@ -6,15 +6,27 @@ #include #include +#include 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, diff --git a/src/game_initialization/multiplayer.cpp b/src/game_initialization/multiplayer.cpp index 15bc9feda8c..d0d0c72d695 100644 --- a/src/game_initialization/multiplayer.cpp +++ b/src/game_initialization/multiplayer.cpp @@ -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) { From 43e3d37a2122ab40fe9df887d971eab80588ff10 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Tue, 4 Nov 2014 21:22:21 -0500 Subject: [PATCH 02/10] fix bug #22484 (fix random map generation in mp create) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As described in bug report, mp create had a bizarre implementation for random map vs scenario generation. Scenario generation was called map generation, and map generation was impossible. We fix it and make it work like it works in the rest of the game. At time of writing the wiki describes map generation wml as follows: ``` To use the default [generator] your [scenario] tag must contain one of the following keys: scenario_generation=default map_generation=default If ‘scenario_generation’ is used, the engine will expect for your entire [scenario] sub tags to be inside a [scenario] tag inside [generator]. Tags outside of this will be ignored. There may be value in this, but at this writing, it’s not clear. ‘map_generation=default’ is simpler and more commonly used. It is also necessary to use this key so that you can regenerate a map in MP game creation. In its use only generator data is in the [generator] tag, all other [scenario] data is placed outside of it. The exception is if you are making an initial MP scenario available in MP game creation, for this a [scenario] tag must appear inside of [generator], containing the [scenario] subtags you want to use. See “data/multiplayer/scenarios/Random_Scenario.cfg” for an example. ``` This commit essentially removes the "exception" pointed out above. After this, the mp create dialog treats map and scenario generation both in the "random maps" classification, and it handles them normally, scenario generation replacing the entire scenario, and map generation replacing only the map data of the scenario. --- .../multiplayer/scenarios/Random_Scenario.cfg | 2 +- .../scenarios/Random_Scenario_Desert.cfg | 2 +- .../scenarios/Random_Scenario_Marsh.cfg | 2 +- .../scenarios/Random_Scenario_Winter.cfg | 2 +- .../scenarios/Random_YAMG_Scenario.cfg | 2 +- src/game_initialization/create_engine.cpp | 97 +++++++++++++++---- src/game_initialization/create_engine.hpp | 10 +- src/tests/gui/test_gui2.cpp | 2 +- 8 files changed, 95 insertions(+), 24 deletions(-) diff --git a/data/multiplayer/scenarios/Random_Scenario.cfg b/data/multiplayer/scenarios/Random_Scenario.cfg index 3c7e6d27da1..bd18d72d1a6 100644 --- a/data/multiplayer/scenarios/Random_Scenario.cfg +++ b/data/multiplayer/scenarios/Random_Scenario.cfg @@ -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" diff --git a/data/multiplayer/scenarios/Random_Scenario_Desert.cfg b/data/multiplayer/scenarios/Random_Scenario_Desert.cfg index 003202a6fbb..3384188894f 100644 --- a/data/multiplayer/scenarios/Random_Scenario_Desert.cfg +++ b/data/multiplayer/scenarios/Random_Scenario_Desert.cfg @@ -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)" diff --git a/data/multiplayer/scenarios/Random_Scenario_Marsh.cfg b/data/multiplayer/scenarios/Random_Scenario_Marsh.cfg index fdf544f48d4..d51fbb9cb48 100644 --- a/data/multiplayer/scenarios/Random_Scenario_Marsh.cfg +++ b/data/multiplayer/scenarios/Random_Scenario_Marsh.cfg @@ -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)" diff --git a/data/multiplayer/scenarios/Random_Scenario_Winter.cfg b/data/multiplayer/scenarios/Random_Scenario_Winter.cfg index 5bb04eda541..8b41723c698 100644 --- a/data/multiplayer/scenarios/Random_Scenario_Winter.cfg +++ b/data/multiplayer/scenarios/Random_Scenario_Winter.cfg @@ -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)" diff --git a/data/multiplayer/scenarios/Random_YAMG_Scenario.cfg b/data/multiplayer/scenarios/Random_YAMG_Scenario.cfg index 64e8ce48931..fb343f4a6e7 100644 --- a/data/multiplayer/scenarios/Random_YAMG_Scenario.cfg +++ b/data/multiplayer/scenarios/Random_YAMG_Scenario.cfg @@ -5,7 +5,7 @@ id=multiplayer_Random_YAMG_Map name= _ "Random map by YAMG" 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 + scenario_generation=yamg [generator] [scenario] name= _ "Random map" diff --git a/src/game_initialization/create_engine.cpp b/src/game_initialization/create_engine.cpp index a60bee8390a..fc3dafcdf98 100644 --- a/src/game_initialization/create_engine.cpp +++ b/src/game_initialization/create_engine.cpp @@ -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,47 @@ 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 (¤t_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; + } + + 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); + } + + //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 +643,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 +800,7 @@ void create_engine::set_current_level(const size_t index) random_map* current_random_map = dynamic_cast(¤t_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 +1061,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(); diff --git a/src/game_initialization/create_engine.hpp b/src/game_initialization/create_engine.hpp index c07abb2e9b2..182722d256f 100644 --- a/src/game_initialization/create_engine.hpp +++ b/src/game_initialization/create_engine.hpp @@ -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 diff --git a/src/tests/gui/test_gui2.cpp b/src/tests/gui/test_gui2.cpp index 19afb9694d9..7155e07a411 100644 --- a/src/tests/gui/test_gui2.cpp +++ b/src/tests/gui/test_gui2.cpp @@ -820,7 +820,7 @@ struct twrapper std::vector 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( From e85eb66f5b2488a5f4db3a9dacf93463e84f07e0 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 5 Nov 2014 14:39:53 -0500 Subject: [PATCH 03/10] put YAMG as a map generator rather than as a scenario generator this way is a lot less buggy, the other way seems to cause mysterious crashes --- .../scenarios/Random_YAMG_Scenario.cfg | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/data/multiplayer/scenarios/Random_YAMG_Scenario.cfg b/data/multiplayer/scenarios/Random_YAMG_Scenario.cfg index fb343f4a6e7..4ab7e7a3f85 100644 --- a/data/multiplayer/scenarios/Random_YAMG_Scenario.cfg +++ b/data/multiplayer/scenarios/Random_YAMG_Scenario.cfg @@ -5,26 +5,8 @@ id=multiplayer_Random_YAMG_Map name= _ "Random map by YAMG" 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." - scenario_generation=yamg + 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] From ab821fcfeb0df5eeb45dccff36a17a7e4f26837d Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 5 Nov 2014 11:03:39 -0500 Subject: [PATCH 04/10] use the "mapgen" log channel rather than engine, for map generators --- src/generators/map_generator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/generators/map_generator.cpp b/src/generators/map_generator.cpp index b1450f3f9db..000b1828a0a 100644 --- a/src/generators/map_generator.cpp +++ b/src/generators/map_generator.cpp @@ -34,9 +34,9 @@ #include -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() { From 2da4c9157a0653b2c10a6d28f7a0c1cd452684c5 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Tue, 4 Nov 2014 15:52:29 -0500 Subject: [PATCH 05/10] add lua map generator type --- src/CMakeLists.txt | 1 + src/SConscript | 1 + src/generators/lua_map_generator.cpp | 98 ++++++++++++++++++++++++++++ src/generators/lua_map_generator.hpp | 53 +++++++++++++++ src/generators/map_create.cpp | 14 ++-- src/wesnoth.cpp | 2 + 6 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 src/generators/lua_map_generator.cpp create mode 100644 src/generators/lua_map_generator.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 005a589d2ec..a57a30d6a64 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/SConscript b/src/SConscript index 9cd0ce97bab..224489e38dd 100644 --- a/src/SConscript +++ b/src/SConscript @@ -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 diff --git a/src/generators/lua_map_generator.cpp b/src/generators/lua_map_generator.cpp new file mode 100644 index 00000000000..e97ad94cf05 --- /dev/null +++ b/src/generators/lua_map_generator.cpp @@ -0,0 +1,98 @@ +/* + Copyright (C) 2014 by Chris Beck + 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 "lua/lauxlib.h" +#include "lua/lua.h" +#include "lua/lualib.h" + +#ifdef DEBUG_LUA +#include "scripting/debug_lua.hpp" +#endif + +#include "scripting/lua_api.hpp" + +#include + +#include + +lua_map_generator::lua_map_generator(const config & cfg) + : id_(cfg["id"]) + , config_name_(cfg["config_name"]) + , create_map_(cfg["create_map"]) + , mState_(luaL_newstate()) +{ + 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); + } + } +} + +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); + } + } + { + int errcode = lua_pcall(mState_, 0, 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); +} diff --git a/src/generators/lua_map_generator.hpp b/src/generators/lua_map_generator.hpp new file mode 100644 index 00000000000..2f6a87784a7 --- /dev/null +++ b/src/generators/lua_map_generator.hpp @@ -0,0 +1,53 @@ +/* + Copyright (C) 2014 by Chris Beck + 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 "map_generator.hpp" + +#include + +class config; +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(); + +private: + std::string id_, config_name_; + + std::string create_map_; + + lua_State * mState_; +}; + +#endif diff --git a/src/generators/map_create.cpp b/src/generators/map_create.cpp index c0d10baebda..6520b5ae10c 100644 --- a/src/generators/map_create.cpp +++ b/src/generators/map_create.cpp @@ -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 +#include 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 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 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()); diff --git a/src/wesnoth.cpp b/src/wesnoth.cpp index 09fc3c06e73..b18ba284292 100644 --- a/src/wesnoth.cpp +++ b/src/wesnoth.cpp @@ -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 #endif From ce99658dfeadcb22482bf37375baf44256459912 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 5 Nov 2014 02:00:32 -0500 Subject: [PATCH 06/10] add lua libs for lua map generator, also add bindings for mt rng Tested to work with this test scenario, and the mainline defaults: [label] x = {X} y = {Y} text = {STRING} [/label] [multiplayer] id=lua_map_gen name= _ "Lua Map Gen Test Scenario" description= _ "test test test of lua map gen" map_generation="lua" [generator] id="test" config_name="Test Lua Map Generator" create_map = << local rng = Rng:create() print(rng:draw()) print(rng:draw()) print(rng:draw()) local w = 50 local h = 40 map="" for y=1,h do local r = rng:draw() % 2 for x=1,w do if x == 10 and y == 10 then map = map .. " 1 " end if x == (w-10) and y == (h-10) then map = map .. " 2 " end if ((x + y) % 2) == r then map = map .. "Gg" else map = map .. "Md" end if x ~= w then map = map .. "," end end map = map .. "\n" end return map >> [/generator] id = foo random_start_time=yes {DEFAULT_SCHEDULE} [event] name=prestart {LABEL 25 20 ("Lua map generator")} [/event] [side] [ai] villages_per_scout=8 [/ai] id=RBY_Side1 side=1 save_id=RBY_Side1 persistent=yes color=red team_name=Red user_team_name= _ "teamname^Red" controller=human canrecruit=yes shroud=no fog=no gold=1000000 [/side] [side] [ai] villages_per_scout=8 [/ai] id=RBY_Side2 side=2 save_id=RBY_Side2 persistent=yes color=blue team_name=Blue user_team_name= _ "teamname^Blue" controller=human canrecruit=yes shroud=no fog=no gold=1000000 [/side] [/multiplayer] --- src/generators/lua_map_generator.cpp | 118 +++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/generators/lua_map_generator.cpp b/src/generators/lua_map_generator.cpp index e97ad94cf05..163d5238349 100644 --- a/src/generators/lua_map_generator.cpp +++ b/src/generators/lua_map_generator.cpp @@ -20,6 +20,8 @@ #include "lua/lua.h" #include "lua/lualib.h" +#include "mt_rng.hpp" + #ifdef DEBUG_LUA #include "scripting/debug_lua.hpp" #endif @@ -30,6 +32,120 @@ #include + +// 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) +{ + static_cast< mt_rng * >( lua_touserdata( L , 1 ) )->~mt_rng(); + return 0; +} +int impl_rng_seed(lua_State* L) +{ + mt_rng * rng = static_cast(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(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"]) @@ -45,6 +161,8 @@ lua_map_generator::lua_map_generator(const config & cfg) throw mapgen_exception(msg); } } + + initialize_lua_state(mState_); } lua_map_generator::~lua_map_generator() From d8fcd104aea19c8f93ad15c24bef6c99a8b209c1 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 5 Nov 2014 02:35:42 -0500 Subject: [PATCH 07/10] add support to create scenarios in lua also --- src/generators/lua_map_generator.cpp | 42 ++++++++++++++++++++++++++++ src/generators/lua_map_generator.hpp | 2 ++ 2 files changed, 44 insertions(+) diff --git a/src/generators/lua_map_generator.cpp b/src/generators/lua_map_generator.cpp index 163d5238349..ba5585056c7 100644 --- a/src/generators/lua_map_generator.cpp +++ b/src/generators/lua_map_generator.cpp @@ -150,6 +150,7 @@ 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()) { const char* required[] = {"id", "config_name", "create_map"}; @@ -214,3 +215,44 @@ std::string lua_map_generator::create_map() } 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); + } + } + { + int errcode = lua_pcall(mState_, 0, 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); +} diff --git a/src/generators/lua_map_generator.hpp b/src/generators/lua_map_generator.hpp index 2f6a87784a7..ddd06876873 100644 --- a/src/generators/lua_map_generator.hpp +++ b/src/generators/lua_map_generator.hpp @@ -41,11 +41,13 @@ public: 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_; }; From a964bd0e886d5ab0b9ba9635c9cd693e26c97b0a Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 5 Nov 2014 04:03:18 -0500 Subject: [PATCH 08/10] pass the [generator] tag as argument to lua map generator functions --- src/generators/lua_map_generator.cpp | 7 +++++-- src/generators/lua_map_generator.hpp | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/generators/lua_map_generator.cpp b/src/generators/lua_map_generator.cpp index ba5585056c7..852f786e707 100644 --- a/src/generators/lua_map_generator.cpp +++ b/src/generators/lua_map_generator.cpp @@ -152,6 +152,7 @@ lua_map_generator::lua_map_generator(const config & cfg) , 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) { @@ -189,7 +190,8 @@ std::string lua_map_generator::create_map() } } { - int errcode = lua_pcall(mState_, 0, 1, 0); + 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"; @@ -238,7 +240,8 @@ config lua_map_generator::create_scenario() } } { - int errcode = lua_pcall(mState_, 0, 1, 0); + 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"; diff --git a/src/generators/lua_map_generator.hpp b/src/generators/lua_map_generator.hpp index ddd06876873..3640f2a8c4c 100644 --- a/src/generators/lua_map_generator.hpp +++ b/src/generators/lua_map_generator.hpp @@ -15,11 +15,11 @@ #ifndef LUA_MAP_GENERATOR_DEFINED #define LUA_MAP_GENERATOR_DEFINED +#include "config.hpp" #include "map_generator.hpp" #include -class config; struct lua_State; // TODO: Add support for user configurability (via defining a gui2 dialog in lua) @@ -50,6 +50,8 @@ private: std::string create_scenario_; lua_State * mState_; + + config generator_data_; }; #endif From 6c85b4f4d80988907f9c45982d1abd49cb24a7f5 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 5 Nov 2014 10:46:25 -0500 Subject: [PATCH 09/10] use our loggers in lua map generator, make a better dtor for Rng --- src/generators/lua_map_generator.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/generators/lua_map_generator.cpp b/src/generators/lua_map_generator.cpp index 852f786e707..e925d7936cd 100644 --- a/src/generators/lua_map_generator.cpp +++ b/src/generators/lua_map_generator.cpp @@ -15,6 +15,7 @@ #include "lua_map_generator.hpp" #include "config.hpp" +#include "log.hpp" #include "lua/lauxlib.h" #include "lua/lua.h" @@ -32,6 +33,10 @@ #include +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__) @@ -125,7 +130,13 @@ int impl_rng_create(lua_State* L) } int impl_rng_destroy(lua_State* L) { - static_cast< mt_rng * >( lua_touserdata( L , 1 ) )->~mt_rng(); + 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) From c785310f05b1f332050e2b50404ea0558681de71 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 5 Nov 2014 16:09:52 -0500 Subject: [PATCH 10/10] create_engine now catches mapgen_exceptions thrown by generators It puts those messages in the error_message field of the map entry, so we get nicer gui handling of the problem. --- src/game_initialization/create_engine.cpp | 50 +++++++++++++---------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/game_initialization/create_engine.cpp b/src/game_initialization/create_engine.cpp index fc3dafcdf98..076b8eaeb1d 100644 --- a/src/game_initialization/create_engine.cpp +++ b/src/game_initialization/create_engine.cpp @@ -470,30 +470,38 @@ void create_engine::init_generated_level_data() return; } - if (!cur_lev->generate_whole_scenario()) - { - DBG_MP << "** replacing map ** \n"; + 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["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; + data["error_message"] = e.what(); cur_lev->set_data(data); }