From e4658ba5ddea3ff3c500c4cf244a70c46a110b5a Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Sat, 20 Feb 2021 09:26:51 -0500 Subject: [PATCH] Refactor the game map to permit exposing it to Lua via wesnoth.current.map The method of accessing terrain on the map has drastically changed. - wesnoth.get_terrain and wesnoth.set_terrain are both deprecated - wesnoth.terrain_mask still works but is moved into the wesnoth.map module and now takes the map object as the first parameter - The map's terrain is now accessed exclusively via indexing on the map object, ie map[{x,y}] - You set terrain by assigning a terrain code; the position of ^ in the terrain code now determines the merge mode - The replace_if_failed option is now manifested as a function that converts any terrain code into a special value that, when assigned to a location on the map, uses the replace if failed logic. The map object has a few attributes in it: - width and height are the total size, including borders - playable_width and playable_height are the values returned from wesnoth.get_map_size, which is now deprecated - border_size is the third value from wesnoth.get_map_size - data converts the map to a string - other than that, wesnoth.map is treated as if it were the metatable of the map object Other stuff in here: - Special locations are now part of the map object. The length operator is deprecated. - Add some utility functions to help clarify the merge mode being used when assigning terrains - they just add or remove a leading or trailing ^ as necessary - Add a utility function to extract a location from the front of a variadic parameter pack --- data/lua/core/map.lua | 40 ++ src/editor/action/action.cpp | 4 +- src/editor/action/mouse/mouse_action.cpp | 6 +- src/editor/map/editor_map.cpp | 118 +++--- src/game_board.cpp | 4 + src/game_board.hpp | 6 +- src/map/map.cpp | 136 ++++--- src/map/map.hpp | 287 +++++++------ src/scripting/game_lua_kernel.cpp | 259 +----------- src/scripting/game_lua_kernel.hpp | 3 - src/scripting/lua_terrainfilter.cpp | 46 +-- src/scripting/lua_terrainfilter.hpp | 3 +- src/scripting/lua_terrainmap.cpp | 487 +++++++++++++---------- src/scripting/lua_terrainmap.hpp | 85 +--- src/scripting/mapgen_lua_kernel.cpp | 2 +- 15 files changed, 646 insertions(+), 840 deletions(-) diff --git a/data/lua/core/map.lua b/data/lua/core/map.lua index f552669ea4700..61c38bb8a9fb7 100644 --- a/data/lua/core/map.lua +++ b/data/lua/core/map.lua @@ -64,4 +64,44 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then end return nil, 0 end + + wesnoth.terrain_mask = wesnoth.deprecate_api('wesnoth.terrain_mask', 'wesnoth.current.map:terrain_mask', 1, nil, function(...) + wesnoth.current.map:terrain_mask(...) + end) + wesnoth.get_terrain = wesnoth.deprecate_api('wesnoth.get_terrain', 'wesnoth.current.map[loc]', 1, nil, function(x, y) + local loc = wesnoth.read_location(x, y) + if loc == nil then error('get_terrain: expected location') end + return wesnoth.current.map[loc] + end) + wesnoth.set_terrain = wesnoth.deprecate_api('wesnoth.set_terrain', 'wesnoth.current.map[loc]=', 1, nil, function(...) + local loc, n = wesnoth.read_location(...) + if n == 0 then error('set_terrain: expected location') end + local new_ter, mode, replace_if_failed = select(n + 1, ...) + if new_ter == '' or type(new_ter) ~= 'string' then error('set_terrain: expected terrain string') end + if replace_if_failed then + mode = mode or 'both' + new_ter = wesnoth.map.replace_if_failed(new_ter, mode, true) + elseif mode == 'both' or mode == 'base' or mode == 'overlay' then + new_ter = wesnoth.map['replace_' .. mode](new_ter) + else + error('set_terrain: invalid mode') + end + wesnoth.current.map[loc] = new_ter + end) + wesnoth.get_map_size = wesnoth.deprecate_api('wesnoth.get_map_size', 'wesnoth.current.map.playable_width,playable_height,border_size', 1, nil, function() + local m = wesnoth.current.map + return m.playable_width, m.playable_height, m.border_size + end) + wesnoth.special_locations = wesnoth.deprecate_api('wesnoth.special_locations', 'wesnoth.current.map:special_locations', 1, nil, setmetatable({}, { + __index = function(_, k) return wesnoth.current.map.special_locations[k] end, + __newindex = function(_, k, v) wesnoth.current.map.special_locations[k] = v end, + __len = function(_) + local n = 0 + for k,v in pairs(wesnoth.current.map.special_locations) do + n = n + 1 + end + return n + end, + __pairs = function(_) return pairs(wesnoth.current.map.special_locations) end, + }), 'Note: the length operator has been removed') end \ No newline at end of file diff --git a/src/editor/action/action.cpp b/src/editor/action/action.cpp index b2f4581fab74c..bd243f6a607d3 100644 --- a/src/editor/action/action.cpp +++ b/src/editor/action/action.cpp @@ -241,7 +241,7 @@ std::unique_ptr editor_action_starting_position::perform(map_cont { std::unique_ptr undo; - const std::string* old_loc_id = mc.map().is_starting_position(loc_); + const std::string* old_loc_id = mc.map().is_special_location(loc_); map_location old_loc = mc.map().special_location(loc_id_); if(old_loc_id != nullptr) { @@ -271,7 +271,7 @@ std::unique_ptr editor_action_starting_position::perform(map_cont void editor_action_starting_position::perform_without_undo(map_context& mc) const { - const std::string* old_id = mc.map().is_starting_position(loc_); + const std::string* old_id = mc.map().is_special_location(loc_); if(old_id != nullptr) { mc.map().set_special_location(*old_id, map_location()); } diff --git a/src/editor/action/mouse/mouse_action.cpp b/src/editor/action/mouse/mouse_action.cpp index 481fba25ffbc1..c7e8c4f85653f 100644 --- a/src/editor/action/mouse/mouse_action.cpp +++ b/src/editor/action/mouse/mouse_action.cpp @@ -106,7 +106,7 @@ std::unique_ptr mouse_action::key_event( || event.key.keysym.sym == SDLK_DELETE) { int res = event.key.keysym.sym - '0'; if (res > gamemap::MAX_PLAYERS || event.key.keysym.sym == SDLK_DELETE) res = 0; - const std::string* old_id = disp.map().is_starting_position(previous_move_hex_); + const std::string* old_id = disp.map().is_special_location(previous_move_hex_); if (res == 0 && old_id != nullptr) { a = std::make_unique(map_location(), *old_id); } else if (res > 0 && (old_id == nullptr || *old_id == std::to_string(res))) { @@ -402,7 +402,7 @@ std::unique_ptr mouse_action_starting_position::up_left(editor_di if (!disp.map().on_board(hex)) { return nullptr; } - auto player_starting_at_hex = disp.map().is_starting_position(hex); + auto player_starting_at_hex = disp.map().is_special_location(hex); if (has_ctrl_modifier()) { if (player_starting_at_hex) { @@ -437,7 +437,7 @@ std::unique_ptr mouse_action_starting_position::click_left(editor std::unique_ptr mouse_action_starting_position::up_right(editor_display& disp, int x, int y) { map_location hex = disp.hex_clicked_on(x, y); - auto player_starting_at_hex = disp.map().is_starting_position(hex); + auto player_starting_at_hex = disp.map().is_special_location(hex); if (player_starting_at_hex != nullptr) { return std::make_unique(map_location(), *player_starting_at_hex); } else { diff --git a/src/editor/map/editor_map.cpp b/src/editor/map/editor_map.cpp index f04c24fc7292b..d09cbf0825822 100644 --- a/src/editor/map/editor_map.cpp +++ b/src/editor/map/editor_map.cpp @@ -84,20 +84,20 @@ editor_map::~editor_map() void editor_map::sanity_check() { int errors = 0; - if (total_width() != tiles_.w) { - ERR_ED << "total_width is " << total_width() << " but tiles_.size() is " << tiles_.w << std::endl; + if (total_width() != tiles().w) { + ERR_ED << "total_width is " << total_width() << " but tiles().size() is " << tiles().w << std::endl; ++errors; } - if (total_height() != tiles_.h) { - ERR_ED << "total_height is " << total_height() << " but tiles_[0].size() is " << tiles_.h << std::endl; + if (total_height() != tiles().h) { + ERR_ED << "total_height is " << total_height() << " but tiles()[0].size() is " << tiles().h << std::endl; ++errors; } if (w() + 2 * border_size() != total_width()) { - ERR_ED << "h is " << h_ << " and border_size is " << border_size() << " but total_width is " << total_width() << std::endl; + ERR_ED << "h is " << h() << " and border_size is " << border_size() << " but total_width is " << total_width() << std::endl; ++errors; } if (h() + 2 * border_size() != total_height()) { - ERR_ED << "w is " << w_ << " and border_size is " << border_size() << " but total_height is " << total_height() << std::endl; + ERR_ED << "w is " << w() << " and border_size is " << border_size() << " but total_height is " << total_height() << std::endl; ++errors; } for (const map_location& loc : selection_) { @@ -137,7 +137,7 @@ std::set editor_map::set_starting_position_labels(display& disp) std::string label; - for (const auto& pair : starting_positions_.left) { + for (const auto& pair : special_locations().left) { bool is_number = std::find_if(pair.first.begin(), pair.first.end(), [](char c) { return !std::isdigit(c); }) == pair.first.end(); if (is_number) { @@ -246,8 +246,8 @@ void editor_map::resize(int width, int height, int x_offset, int y_offset, // fix the starting positions if(x_offset || y_offset) { - for (auto it = starting_positions_.left.begin(); it != starting_positions_.left.end(); ++it) { - starting_positions_.left.modify_data(it, [=](t_translation::coordinate & loc) { loc.add(-x_offset, -y_offset); }); + for (auto it = special_locations().left.begin(); it != special_locations().left.end(); ++it) { + special_locations().left.modify_data(it, [=](t_translation::coordinate & loc) { loc.add(-x_offset, -y_offset); }); } } @@ -298,130 +298,122 @@ bool editor_map::same_size_as(const gamemap& other) const void editor_map::expand_right(int count, const t_translation::terrain_code & filler) { - t_translation::ter_map tiles_new(tiles_.w + count, tiles_.h); - w_ += count; - for (int x = 0, x_end = tiles_.w; x != x_end; ++x) { - for (int y = 0, y_end = tiles_.h; y != y_end; ++y) { - tiles_new.get(x, y) = tiles_.get(x, y); + t_translation::ter_map tiles_new(tiles().w + count, tiles().h); + for (int x = 0, x_end = tiles().w; x != x_end; ++x) { + for (int y = 0, y_end = tiles().h; y != y_end; ++y) { + tiles_new.get(x, y) = tiles().get(x, y); } } - for (int x = tiles_.w, x_end = tiles_.w + count; x != x_end; ++x) { - for (int y = 0, y_end = tiles_.h; y != y_end; ++y) { - tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles_.get(tiles_.w - 1, y) : filler; + for (int x = tiles().w, x_end = tiles().w + count; x != x_end; ++x) { + for (int y = 0, y_end = tiles().h; y != y_end; ++y) { + tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(tiles().w - 1, y) : filler; } } - tiles_ = std::move(tiles_new); + tiles() = std::move(tiles_new); } void editor_map::expand_left(int count, const t_translation::terrain_code & filler) { - t_translation::ter_map tiles_new(tiles_.w + count, tiles_.h); - w_ += count; - for (int x = 0, x_end = tiles_.w; x != x_end; ++x) { - for (int y = 0, y_end = tiles_.h; y != y_end; ++y) { - tiles_new.get(x + count, y) = tiles_.get(x, y); + t_translation::ter_map tiles_new(tiles().w + count, tiles().h); + for (int x = 0, x_end = tiles().w; x != x_end; ++x) { + for (int y = 0, y_end = tiles().h; y != y_end; ++y) { + tiles_new.get(x + count, y) = tiles().get(x, y); } } for (int x = 0, x_end = count; x != x_end; ++x) { - for (int y = 0, y_end = tiles_.h; y != y_end; ++y) { - tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles_.get(0, y) : filler; + for (int y = 0, y_end = tiles().h; y != y_end; ++y) { + tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(0, y) : filler; } } - tiles_ = std::move(tiles_new); + tiles() = std::move(tiles_new); } void editor_map::expand_top(int count, const t_translation::terrain_code & filler) { - t_translation::ter_map tiles_new(tiles_.w, tiles_.h + count); - h_ += count; - for (int x = 0, x_end = tiles_.w; x != x_end; ++x) { - for (int y = 0, y_end = tiles_.h; y != y_end; ++y) { - tiles_new.get(x, y + count) = tiles_.get(x, y); + t_translation::ter_map tiles_new(tiles().w, tiles().h + count); + for (int x = 0, x_end = tiles().w; x != x_end; ++x) { + for (int y = 0, y_end = tiles().h; y != y_end; ++y) { + tiles_new.get(x, y + count) = tiles().get(x, y); } } - for (int x = 0, x_end = tiles_.w; x != x_end; ++x) { + for (int x = 0, x_end = tiles().w; x != x_end; ++x) { for (int y = 0, y_end = count; y != y_end; ++y) { - tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles_.get(x, 0) : filler; + tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(x, 0) : filler; } } - tiles_ = std::move(tiles_new); + tiles() = std::move(tiles_new); } void editor_map::expand_bottom(int count, const t_translation::terrain_code & filler) { - t_translation::ter_map tiles_new(tiles_.w, tiles_.h + count); - h_ += count; - for (int x = 0, x_end = tiles_.w; x != x_end; ++x) { - for (int y = 0, y_end = tiles_.h; y != y_end; ++y) { - tiles_new.get(x, y) = tiles_.get(x, y); + t_translation::ter_map tiles_new(tiles().w, tiles().h + count); + for (int x = 0, x_end = tiles().w; x != x_end; ++x) { + for (int y = 0, y_end = tiles().h; y != y_end; ++y) { + tiles_new.get(x, y) = tiles().get(x, y); } } - for (int x = 0, x_end = tiles_.w; x != x_end; ++x) { - for (int y = tiles_.h, y_end = tiles_.h + count; y != y_end; ++y) { - tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles_.get(x, tiles_.h - 1) : filler; + for (int x = 0, x_end = tiles().w; x != x_end; ++x) { + for (int y = tiles().h, y_end = tiles().h + count; y != y_end; ++y) { + tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(x, tiles().h - 1) : filler; } } - tiles_ = std::move(tiles_new); + tiles() = std::move(tiles_new); } void editor_map::shrink_right(int count) { - if(count < 0 || count > tiles_.w) { + if(count < 0 || count > tiles().w) { throw editor_map_operation_exception(); } - t_translation::ter_map tiles_new(tiles_.w - count, tiles_.h); + t_translation::ter_map tiles_new(tiles().w - count, tiles().h); for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) { for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) { - tiles_new.get(x, y) = tiles_.get(x, y); + tiles_new.get(x, y) = tiles().get(x, y); } } - w_ -= count; - tiles_ = std::move(tiles_new); + tiles() = std::move(tiles_new); } void editor_map::shrink_left(int count) { - if (count < 0 || count > tiles_.w) { + if (count < 0 || count > tiles().w) { throw editor_map_operation_exception(); } - t_translation::ter_map tiles_new(tiles_.w - count, tiles_.h); + t_translation::ter_map tiles_new(tiles().w - count, tiles().h); for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) { for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) { - tiles_new.get(x, y) = tiles_.get(x + count, y); + tiles_new.get(x, y) = tiles().get(x + count, y); } } - w_ -= count; - tiles_ = std::move(tiles_new); + tiles() = std::move(tiles_new); } void editor_map::shrink_top(int count) { - if (count < 0 || count > tiles_.h) { + if (count < 0 || count > tiles().h) { throw editor_map_operation_exception(); } - t_translation::ter_map tiles_new(tiles_.w, tiles_.h - count); + t_translation::ter_map tiles_new(tiles().w, tiles().h - count); for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) { for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) { - tiles_new.get(x, y) = tiles_.get(x, y + count); + tiles_new.get(x, y) = tiles().get(x, y + count); } } - h_ -= count; - tiles_ = std::move(tiles_new); + tiles() = std::move(tiles_new); } void editor_map::shrink_bottom(int count) { - if (count < 0 || count > tiles_.h) { + if (count < 0 || count > tiles().h) { throw editor_map_operation_exception(); } - t_translation::ter_map tiles_new(tiles_.w, tiles_.h - count); + t_translation::ter_map tiles_new(tiles().w, tiles().h - count); for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) { for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) { - tiles_new.get(x, y) = tiles_.get(x, y); + tiles_new.get(x, y) = tiles().get(x, y); } } - h_ -= count; - tiles_ = std::move(tiles_new); + tiles() = std::move(tiles_new); } diff --git a/src/game_board.cpp b/src/game_board.cpp index 4fefdb4df5d68..77dc77695f9e3 100644 --- a/src/game_board.cpp +++ b/src/game_board.cpp @@ -337,7 +337,11 @@ bool game_board::change_terrain( } else if(mode_str == "overlay") { mode = terrain_type_data::OVERLAY; } + + return change_terrain(loc, terrain, mode, replace_if_failed); +} +bool game_board::change_terrain(const map_location &loc, const t_translation::terrain_code &terrain, terrain_type_data::merge_mode& mode, bool replace_if_failed) { /* * When a hex changes from a village terrain to a non-village terrain, and * a team owned that village it loses that village. When a hex changes from diff --git a/src/game_board.hpp b/src/game_board.hpp index 24ea450e8145f..1386fdabddf75 100644 --- a/src/game_board.hpp +++ b/src/game_board.hpp @@ -16,6 +16,8 @@ #include "display_context.hpp" #include "team.hpp" +#include "terrain/translation.hpp" +#include "terrain/type_data.hpp" #include "units/map.hpp" #include "units/id.hpp" #include @@ -157,8 +159,8 @@ class game_board : public display_context bool try_add_unit_to_recall_list(const map_location& loc, const unit_ptr u); std::optional replace_map(const gamemap & r); - bool change_terrain(const map_location &loc, const std::string &t, - const std::string & mode, bool replace_if_failed); //used only by lua and debug commands + bool change_terrain(const map_location &loc, const std::string &t, const std::string & mode, bool replace_if_failed); //used only by lua and debug commands + bool change_terrain(const map_location &loc, const t_translation::terrain_code &t, terrain_type_data::merge_mode& mode, bool replace_if_failed); //used only by lua and debug commands // Global accessor from unit.hpp diff --git a/src/map/map.cpp b/src/map/map.cpp index 810d44e5a959b..39041af968ec2 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -28,6 +28,7 @@ #include "serialization/string_utils.hpp" #include "terrain/terrain.hpp" #include "terrain/type_data.hpp" +#include "wml_exception.hpp" #include #include @@ -101,12 +102,10 @@ void gamemap::write_terrain(const map_location &loc, config& cfg) const cfg["terrain"] = t_translation::write_terrain_code(get_terrain(loc)); } -gamemap::gamemap(const std::string& data) - : tiles_(1, 1) - , tdata_() - , villages_() - , w_(-1) - , h_(-1) +gamemap::gamemap(const std::string& data): + gamemap_base(1, 1), + tdata_(), + villages_() { if(const auto* gcm = game_config_manager::get()) { tdata_ = gcm->terrain_types(); @@ -119,19 +118,24 @@ gamemap::gamemap(const std::string& data) read(data); } -gamemap::~gamemap() +gamemap_base::gamemap_base(int w, int h, terrain_code t) + : tiles_(w, h, t) + , starting_positions_() +{ + +} + +gamemap_base::~gamemap_base() { } void gamemap::read(const std::string& data, const bool allow_invalid) { - tiles_ = t_translation::ter_map(); + tiles() = t_translation::ter_map(); villages_.clear(); - starting_positions_.clear(); + special_locations().clear(); if(data.empty()) { - w_ = 0; - h_ = 0; if(allow_invalid) return; } @@ -140,7 +144,7 @@ void gamemap::read(const std::string& data, const bool allow_invalid) const std::string& data_only = std::string(data, offset); try { - tiles_ = t_translation::read_game_map(data_only, starting_positions_, t_translation::coordinate{ border_size(), border_size() }); + tiles() = t_translation::read_game_map(data_only, special_locations(), t_translation::coordinate{ border_size(), border_size() }); } catch(const t_translation::error& e) { // We re-throw the error but as map error. @@ -149,18 +153,13 @@ void gamemap::read(const std::string& data, const bool allow_invalid) } // Post processing on the map - w_ = total_width() - 2 * border_size(); - h_ = total_height() - 2 * border_size(); - //Disabled since there are callcases which pass along a valid map header but empty - //map data. Still, loading (and actually applying) an empty map causes problems later on. - //Other callcases which need to load a dummy map use completely empty data :(. - //VALIDATE((w_ >= 1 && h_ >= 1), "A map needs at least 1 tile, the map cannot be loaded."); + VALIDATE((total_width() >= 1 && total_height() >= 1), "A map needs at least 1 tile, the map cannot be loaded."); for(int x = 0; x < total_width(); ++x) { for(int y = 0; y < total_height(); ++y) { // Is the terrain valid? - t_translation::terrain_code t = tiles_.get(x, y); + t_translation::terrain_code t = tiles().get(x, y); if(tdata_->map().count(t) == 0) { if(!tdata_->is_known(t)) { std::stringstream ss; @@ -173,7 +172,7 @@ void gamemap::read(const std::string& data, const bool allow_invalid) // Is it a village? if(x >= border_size() && y >= border_size() && x < total_width()- border_size() && y < total_height()- border_size() - && tdata_->is_village(tiles_.get(x, y))) { + && tdata_->is_village(tiles().get(x, y))) { villages_.push_back(map_location(x - border_size(), y - border_size())); } } @@ -209,40 +208,24 @@ int gamemap::read_header(const std::string& data) std::string gamemap::write() const { - return t_translation::write_game_map(tiles_, starting_positions_, t_translation::coordinate{ border_size(), border_size() }) + "\n"; -} - -void gamemap::overlay(const gamemap& m, map_location loc, const std::vector& rules, bool m_is_odd, bool ignore_special_locations) -{ - //the following line doesn't compile on all compiler without the 'this->' - overlay_impl(tiles_, starting_positions_, m.tiles_, m.starting_positions_, [this](auto&&... arg) { this->set_terrain(std::forward(arg)...); }, loc, rules, m_is_odd, ignore_special_locations); + return t_translation::write_game_map(tiles(), special_locations(), t_translation::coordinate{ border_size(), border_size() }) + "\n"; } -void gamemap::overlay_impl( - // const but changed via set_terrain - const t_translation::ter_map& m1, - starting_positions& m1_st, - const t_translation::ter_map& m2, - const starting_positions& m2_st, - std::function set_terrain, - map_location loc, - const std::vector& rules, - bool m_is_odd, - bool ignore_special_locations) +void gamemap_base::overlay(const gamemap_base& m, map_location loc, const std::vector& rules, bool m_is_odd, bool ignore_special_locations) { int xpos = loc.wml_x(); int ypos = loc.wml_y(); const int xstart = std::max(0, -xpos); - const int xend = std::min(m2.w, m1.w -xpos); + const int xend = std::min(m.total_width(), total_width() - xpos); const int xoffset = xpos; const int ystart_even = std::max(0, -ypos); - const int yend_even = std::min(m2.h, m1.h - ypos); + const int yend_even = std::min(m.total_height(), total_height() - ypos); const int yoffset_even = ypos; const int ystart_odd = std::max(0, -ypos +(xpos & 1) -(m_is_odd ? 1 : 0)); - const int yend_odd = std::min(m2.h, m1.h - ypos +(xpos & 1) -(m_is_odd ? 1 : 0)); + const int yend_odd = std::min(m.total_height(), total_height() - ypos +(xpos & 1) -(m_is_odd ? 1 : 0)); const int yoffset_odd = ypos -(xpos & 1) + (m_is_odd ? 1 : 0); for(int x1 = xstart; x1 != xend; ++x1) { @@ -257,8 +240,8 @@ void gamemap::overlay_impl( const int x2 = x1 + xoffset; const int y2 = y1 + yoffset; - const t_translation::terrain_code t = m2.get(x1,y1); - const t_translation::terrain_code current = m1.get(x2, y2); + const t_translation::terrain_code t = m.get_terrain({x1,y1}); + const t_translation::terrain_code current = get_terrain({x2, y2}); if(t == t_translation::FOGGED || t == t_translation::VOID_TERRAIN) { continue; @@ -288,7 +271,7 @@ void gamemap::overlay_impl( } if (!ignore_special_locations) { - for(auto& pair : m2_st.left) { + for(auto& pair : m.special_locations().left) { int x = pair.second.wml_x(); int y = pair.second.wml_y(); @@ -306,22 +289,22 @@ void gamemap::overlay_impl( int y_new = y + ((x & 1 ) ? yoffset_odd : yoffset_even); map_location pos_new = map_location(x_new, y_new, wml_loc()); - m1_st.left.erase(pair.first); - m1_st.insert(starting_positions::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y))); + starting_positions_.left.erase(pair.first); + starting_positions_.insert(location_map::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y))); } } } -t_translation::terrain_code gamemap::get_terrain(const map_location& loc) const +t_translation::terrain_code gamemap_base::get_terrain(const map_location& loc) const { if(on_board_with_border(loc)) { - return (*this)[loc]; + return tiles_.get(loc.x + border_size(), loc.y + border_size()); } return loc == map_location::null_location() ? t_translation::NONE_TERRAIN : t_translation::terrain_code(); } -map_location gamemap::special_location(const std::string& id) const +map_location gamemap_base::special_location(const std::string& id) const { auto it = starting_positions_.left.find(id); if (it != starting_positions_.left.end()) { @@ -333,31 +316,46 @@ map_location gamemap::special_location(const std::string& id) const } } -map_location gamemap::starting_position(int n) const +map_location gamemap_base::starting_position(int n) const { return special_location(std::to_string(n)); } -int gamemap::num_valid_starting_positions() const +namespace { + bool is_number(const std::string& id) { + return std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end(); + } +} + +int gamemap_base::num_valid_starting_positions() const { int res = 0; for (auto pair : starting_positions_) { const std::string& id = pair.left; - bool is_number = std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end(); - if (is_number) { + if (is_number(id)) { res = std::max(res, std::stoi(id)); } } return res; } -const std::string* gamemap::is_starting_position(const map_location& loc) const +int gamemap_base::is_starting_position(const map_location& loc) const +{ + if(const std::string* locName = is_special_location(loc)) { + if(is_number(*locName)) { + return std::stoi(*locName); + } + } + return 0; +} + +const std::string* gamemap_base::is_special_location(const map_location& loc) const { auto it = starting_positions_.right.find(loc); return it == starting_positions_.right.end() ? nullptr : &it->second; } -void gamemap::set_special_location(const std::string& id, const map_location& loc) +void gamemap_base::set_special_location(const std::string& id, const map_location& loc) { bool valid = loc.valid(); auto it_left = starting_positions_.left.find(id); @@ -374,21 +372,21 @@ void gamemap::set_special_location(const std::string& id, const map_location& lo } } -void gamemap::set_starting_position(int side, const map_location& loc) +void gamemap_base::set_starting_position(int side, const map_location& loc) { set_special_location(std::to_string(side), loc); } -bool gamemap::on_board(const map_location& loc) const +bool gamemap_base::on_board(const map_location& loc) const { - return loc.valid() && loc.x < w_ && loc.y < h_; + return loc.valid() && loc.x < w() && loc.y < h(); } -bool gamemap::on_board_with_border(const map_location& loc) const +bool gamemap_base::on_board_with_border(const map_location& loc) const { return !tiles_.data.empty() && // tiles_ is not empty when initialized. - loc.x >= -border_size() && loc.x < w_ + border_size() && - loc.y >= -border_size() && loc.y < h_ + border_size(); + loc.x >= -border_size() && loc.x < w() + border_size() && + loc.y >= -border_size() && loc.y < h() + border_size(); } void gamemap::set_terrain(const map_location& loc, const t_translation::terrain_code & terrain, const terrain_type_data::merge_mode mode, bool replace_if_failed) { @@ -418,7 +416,7 @@ void gamemap::set_terrain(const map_location& loc, const t_translation::terrain_ (*this)[loc] = new_terrain; } -std::vector gamemap::parse_location_range(const std::string &x, const std::string &y, +std::vector gamemap_base::parse_location_range(const std::string &x, const std::string &y, bool with_border) const { std::vector res; @@ -463,3 +461,17 @@ std::vector gamemap::parse_location_range(const std::string &x, co } return res; } + +std::string gamemap_base::to_string() const +{ + return t_translation::write_game_map(tiles_, starting_positions_, { 1, 1 }) + "\n"; +} + +const std::vector gamemap_base::starting_positions() const { + int n = num_valid_starting_positions(); + std::vector res; + for(int i = 1; i <= n; i++) { + res.push_back(starting_position(i)); + } + return res; +} diff --git a/src/map/map.hpp b/src/map/map.hpp index 28b5a0ef37171..4d6053ba7a847 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -23,6 +23,143 @@ class config; //class terrain_type_data; Can't forward declare because of enum +// This could be moved to a separate file pair... +class gamemap_base +{ +public: + using terrain_code = t_translation::terrain_code; + using terrain_map = t_translation::ter_map; + using location_map = t_translation::starting_positions; + virtual ~gamemap_base(); + + /** The default border style for a map. */ + static const int default_border = 1; + + /** + * Maximum number of players supported. + * + * Warning: when you increase this, you need to add + * more definitions to the team_colors.cfg file. + */ + static const int MAX_PLAYERS = 9; + + std::string to_string() const; + + /** Effective map width. */ + int w() const { return total_width() - 2 * border_size(); } + + /** Effective map height. */ + int h() const { return total_height() - 2 * border_size(); } + + /** Size of the map border. */ + int border_size() const { return default_border; } + + /** Real width of the map, including borders. */ + int total_width() const { return tiles_.w; } + + /** Real height of the map, including borders */ + int total_height() const { return tiles_.h; } + + /** Tell if the map is of 0 size. */ + bool empty() const + { + return w() <= 0 || h() <= 0; + } + + /** + * Tell if a location is on the map. + */ + bool on_board(const map_location& loc) const; + bool on_board_with_border(const map_location& loc) const; + + /** + * Clobbers over the terrain at location 'loc', with the given terrain. + * Uses mode and replace_if_failed like merge_terrains(). + */ + virtual void set_terrain(const map_location& loc, const terrain_code & terrain, const terrain_type_data::merge_mode mode = terrain_type_data::BOTH, bool replace_if_failed = false) = 0; + + /** + * Looks up terrain at a particular location. + * + * Hexes off the map may be looked up, and their 'emulated' terrain will + * also be returned. This allows proper drawing of the edges of the map. + */ + terrain_code get_terrain(const map_location& loc) const; + + location_map& special_locations() { return starting_positions_; } + const location_map& special_locations() const { return starting_positions_; } + const std::vector starting_positions() const; + + void set_special_location(const std::string& id, const map_location& loc); + map_location special_location(const std::string& id) const; + + /** Manipulate starting positions of the different sides. */ + void set_starting_position(int side, const map_location& loc); + map_location starting_position(int side) const; + + /** Counts the number of sides that have valid starting positions on this map */ + int num_valid_starting_positions() const; + /** returns the side number of the side starting at position loc, 0 if no such side exists. */ + int is_starting_position(const map_location& loc) const; + /** returns the name of the special location at position loc, null if no such location exists. */ + const std::string* is_special_location(const map_location& loc) const; + + /** Parses ranges of locations into a vector of locations, using this map's dimensions as bounds. */ + std::vector parse_location_range(const std::string& xvals, const std::string &yvals, bool with_border = false) const; + + struct overlay_rule + { + t_translation::ter_list old_; + t_translation::ter_list new_; + terrain_type_data::merge_mode mode_; + std::optional terrain_; + bool use_old_; + bool replace_if_failed_; + + overlay_rule() + : old_() + , new_() + , mode_(terrain_type_data::BOTH) + , terrain_() + , use_old_(false) + , replace_if_failed_(false) + { + + } + }; + + /** Overlays another map onto this one at the given position. */ + void overlay(const gamemap_base& m, map_location loc, const std::vector& rules = std::vector(), bool is_odd = false, bool ignore_special_locations = false); + + template + void for_each_loc(const F& f) const + { + for(int x = 0; x < total_width(); ++x) { + for(int y = 0; y < total_height(); ++y) { + f(map_location{x, y , wml_loc()}); + } + } + } + //Doesn't include border. + template + void for_each_walkable_loc(const F& f) const + { + for(int x = 0; x < w(); ++x) { + for(int y = 0; y < h(); ++y) { + f(map_location{x, y}); + } + } + } +protected: + gamemap_base() = default; + gamemap_base(int w, int h, terrain_code default_ter = terrain_code()); + terrain_map& tiles() {return tiles_;} + const terrain_map& tiles() const {return tiles_;} +private: + terrain_map tiles_; + location_map starting_positions_; +}; + /** * Encapsulates the map of the game. * @@ -30,7 +167,7 @@ class config; * Each type of terrain is represented by a multiletter terrain code. * @todo Update for new map-format. */ -class gamemap +class gamemap : public gamemap_base { public: @@ -75,115 +212,26 @@ class gamemap */ gamemap(const std::string& data); // throw(incorrect_map_format_error) - virtual ~gamemap(); - void read(const std::string& data, const bool allow_invalid = true); std::string write() const; - struct overlay_rule - { - t_translation::ter_list old_; - t_translation::ter_list new_; - terrain_type_data::merge_mode mode_; - std::optional terrain_; - bool use_old_; - bool replace_if_failed_; - - overlay_rule() - : old_() - , new_() - , mode_(terrain_type_data::BOTH) - , terrain_() - , use_old_(false) - , replace_if_failed_(false) - { - - } - }; - - /** Overlays another map onto this one at the given position. */ - void overlay(const gamemap& m, map_location loc, const std::vector& rules = std::vector(), bool is_odd = false, bool ignore_special_locations = false); - - static void overlay_impl( - // const but changed via set_terrain - const t_translation::ter_map& m1, - t_translation::starting_positions& m1_st, - const t_translation::ter_map& m2, - const t_translation::starting_positions& m2_st, - std::function set_terrain, - map_location loc, - const std::vector& rules, - bool is_odd, - bool ignore_special_locations); - - /** Effective map width, in hexes. */ - int w() const { return w_; } - - /** Effective map height, in hexes. */ - int h() const { return h_; } - - /** Size of the map border. */ - int border_size() const { return default_border; } - - /** Real width of the map, including borders. */ - int total_width() const { return tiles_.w; } - - /** Real height of the map, including borders */ - int total_height() const { return tiles_.h; } - const t_translation::terrain_code operator[](const map_location& loc) const { - return tiles_.get(loc.x + border_size(), loc.y + border_size()); + return tiles().get(loc.x + border_size(), loc.y + border_size()); } private: //private method, use set_terrain instead which also updates villages_. t_translation::terrain_code& operator[](const map_location& loc) { - return tiles_.get(loc.x + border_size(), loc.y + border_size()); + return tiles().get(loc.x + border_size(), loc.y + border_size()); } public: - - /** - * Looks up terrain at a particular location. - * - * Hexes off the map may be looked up, and their 'emulated' terrain will - * also be returned. This allows proper drawing of the edges of the map. - */ - t_translation::terrain_code get_terrain(const map_location& loc) const; + void set_terrain(const map_location& loc, const terrain_code & terrain, const terrain_type_data::merge_mode mode = terrain_type_data::BOTH, bool replace_if_failed = false) override; /** Writes the terrain at loc to cfg. */ void write_terrain(const map_location &loc, config& cfg) const; - - /** Manipulate starting positions of the different sides. */ - void set_starting_position(int side, const map_location& loc); - map_location starting_position(int side) const; - - void set_special_location(const std::string& id, const map_location& loc); - map_location special_location(const std::string& id) const; - - - /** returns the side number of the side starting at position loc, 0 if no such side exists. */ - const std::string* is_starting_position(const map_location& loc) const; - int num_valid_starting_positions() const; - - - /** - * Tell if a location is on the map. - * - * Should be called before indexing using []. - * @todo inline for performance? -- Ilor - */ - bool on_board(const map_location& loc) const; - bool on_board_with_border(const map_location& loc) const; - - /** Tell if the map is of 0 size. */ - bool empty() const - { - return w_ == 0 || h_ == 0; - } - /** Return a list of the locations of villages on the map. */ const std::vector& villages() const { return villages_; } @@ -193,55 +241,6 @@ class gamemap /** Gets the list of terrains. */ const t_translation::ter_list& get_terrain_list() const; - /** - * Clobbers over the terrain at location 'loc', with the given terrain. - * Uses mode and replace_if_failed like merge_terrains(). - */ - void set_terrain(const map_location& loc, const t_translation::terrain_code & terrain, const terrain_type_data::merge_mode mode=terrain_type_data::BOTH, bool replace_if_failed = false); - - /** - * Maximum number of players supported. - * - * Warning: when you increase this, you need to add - * more definitions to the team_colors.cfg file. - */ - enum { MAX_PLAYERS = 9 }; - - /** The default border style for a map. */ - static const int default_border = 1; - - /** Parses ranges of locations into a vector of locations, using this map's dimensions as bounds. */ - std::vector parse_location_range(const std::string& xvals, - const std::string &yvals, bool with_border = false) const; - - using starting_positions = t_translation::starting_positions; - const starting_positions& special_locations() const { return starting_positions_; } - - template - void for_each_loc(const F& f) const - { - for (int x = -border_size(); x < w() + border_size(); ++x) { - for (int y = -border_size(); y < h() + border_size(); ++y) { - f({ x, y }); - } - } - } - //Doesn't include border. - template - void for_each_walkable_loc(const F& f) const - { - for (int x = 0; x < w(); ++x) { - for (int y = 0; y < h(); ++y) { - f({ x, y }); - } - } - } - -protected: - t_translation::ter_map tiles_; - - starting_positions starting_positions_; - private: /** @@ -255,8 +254,4 @@ class gamemap protected: std::vector villages_; - - /** Sizes of the map area. */ - int w_; - int h_; }; diff --git a/src/scripting/game_lua_kernel.cpp b/src/scripting/game_lua_kernel.cpp index c649456262bf8..a4e9c1ab69343 100644 --- a/src/scripting/game_lua_kernel.cpp +++ b/src/scripting/game_lua_kernel.cpp @@ -79,6 +79,7 @@ #include "scripting/lua_pathfind_cost_calculator.hpp" #include "scripting/lua_race.hpp" #include "scripting/lua_team.hpp" +#include "scripting/lua_terrainmap.hpp" #include "scripting/lua_unit_type.hpp" #include "scripting/push_check.hpp" #include "synced_commands.hpp" @@ -211,83 +212,6 @@ std::vector game_lua_kernel::get_sides_vector(const vconfig& cfg) return filter.get_teams(); } - - -static int special_locations_len(lua_State *L) -{ - lua_pushnumber(L, lua_kernel_base::get_lua_kernel(L).map().special_locations().size()); - return 1; -} - -static int special_locations_next(lua_State *L) -{ - const t_translation::starting_positions::left_map& left = lua_kernel_base::get_lua_kernel(L).map().special_locations().left; - - t_translation::starting_positions::left_const_iterator it; - if (lua_isnoneornil(L, 2)) { - it = left.begin(); - } - else { - it = left.find(luaL_checkstring(L, 2)); - if (it == left.end()) { - return 0; - } - ++it; - } - if (it == left.end()) { - return 0; - } - lua_pushstring(L, it->first.c_str()); - luaW_pushlocation(L, it->second); - return 2; -} - -static int special_locations_pairs(lua_State *L) -{ - lua_pushcfunction(L, &special_locations_next); - lua_pushvalue(L, -2); - lua_pushnil(L); - return 3; -} - -static int special_locations_index(lua_State *L) -{ - const t_translation::starting_positions::left_map& left = lua_kernel_base::get_lua_kernel(L).map().special_locations().left; - auto it = left.find(luaL_checkstring(L, 2)); - if (it == left.end()) { - return 0; - } - else { - luaW_pushlocation(L, it->second); - return 1; - } -} - -static int special_locations_newindex(lua_State *L) -{ - lua_pushstring(L, "special locations cannot be modified using wesnoth.special_locations"); - return lua_error(L); -} - -static void push_locations_table(lua_State *L) -{ - lua_newtable(L); // The functor table - lua_newtable(L); // The functor metatable - lua_pushstring(L, "__len"); - lua_pushcfunction(L, &special_locations_len); - lua_rawset(L, -3); - lua_pushstring(L, "__index"); - lua_pushcfunction(L, &special_locations_index); - lua_rawset(L, -3); - lua_pushstring(L, "__newindex"); - lua_pushcfunction(L, &special_locations_newindex); - lua_rawset(L, -3); - lua_pushstring(L, "__pairs"); - lua_pushcfunction(L, &special_locations_pairs); - lua_rawset(L, -3); - lua_setmetatable(L, -2); // Apply the metatable to the functor table -} - namespace { /** * Temporary entry to a queued_event stack @@ -939,150 +863,6 @@ int game_lua_kernel::intf_lock_view(lua_State *L) return 0; } -/** - * Gets a terrain code. - * - Arg 1: map location. - * - Ret 1: string. - */ -int game_lua_kernel::intf_get_terrain(lua_State *L) -{ - map_location loc = luaW_checklocation(L, 1); - - const t_translation::terrain_code& t = board().map(). - get_terrain(loc); - lua_pushstring(L, t_translation::write_terrain_code(t).c_str()); - return 1; -} - -/** - * Sets a terrain code. - * - Arg 1: map location. - * - Arg 2: terrain code string. - * - Arg 3: layer: (overlay|base|both, default=both) - * - Arg 4: replace_if_failed, default = no - */ -int game_lua_kernel::intf_set_terrain(lua_State *L) -{ - map_location loc = luaW_checklocation(L, 1); - std::string t_str(luaL_checkstring(L, 2)); - - std::string mode_str = "both"; - bool replace_if_failed = false; - if (!lua_isnone(L, 3)) { - if (!lua_isnil(L, 3)) { - mode_str = luaL_checkstring(L, 3); - } - - if(!lua_isnoneornil(L, 4)) { - replace_if_failed = luaW_toboolean(L, 4); - } - } - - bool result = board().change_terrain(loc, t_str, mode_str, replace_if_failed); - - if (game_display_) { - game_display_->needs_rebuild(result); - } - - return 0; -} - -/** - * Reaplces part of the map. - * - Arg 1: map location. - * - Arg 2: map data string. - * - Arg 3: table for optional named arguments - * - is_odd: boolen, if Arg2 has the odd mapo format (as if it was cut from a odd map location) - * - ignore_special_locations: boolean - * - rules: table of tables -*/ -int game_lua_kernel::intf_terrain_mask(lua_State *L) -{ - map_location loc = luaW_checklocation(L, 1); - std::string t_str(luaL_checkstring(L, 2)); - bool is_odd = false; - bool ignore_special_locations = false; - std::vector rules; - - if(lua_istable(L, 3)) { - if(luaW_tableget(L, 3, "is_odd")) { - is_odd = luaW_toboolean(L, -1); - lua_pop(L, 1); - } - if(luaW_tableget(L, 3, "ignore_special_locations")) { - ignore_special_locations = luaW_toboolean(L, -1); - lua_pop(L, 1); - } - if(luaW_tableget(L, 3, "rules")) { - //todo: reduce code dublication by using read_rules_vector. - if(!lua_istable(L, -1)) { - return luaL_argerror(L, 3, "rules must be a table"); - } - - for (int i = 1, i_end = lua_rawlen(L, -1); i <= i_end; ++i) - { - lua_rawgeti(L, -1, i); - if(!lua_istable(L, -1)) { - return luaL_argerror(L, 3, "rules must be a table of tables"); - } - rules.push_back(gamemap::overlay_rule()); - auto& rule = rules.back(); - if(luaW_tableget(L, -1, "old")) { - rule.old_ = t_translation::read_list(luaW_tostring(L, -1)); - lua_pop(L, 1); - } - - if(luaW_tableget(L, -1, "new")) { - rule.new_ = t_translation::read_list(luaW_tostring(L, -1)); - lua_pop(L, 1); - } - - if(luaW_tableget(L, -1, "mode")) { - auto str = luaW_tostring(L, -1); - rule.mode_ = str == "base" ? terrain_type_data::BASE : (str == "overlay" ? terrain_type_data::OVERLAY : terrain_type_data::BOTH); - lua_pop(L, 1); - } - - if(luaW_tableget(L, -1, "terrain")) { - const t_translation::ter_list terrain = t_translation::read_list(luaW_tostring(L, -1)); - if(!terrain.empty()) { - rule.terrain_ = terrain[0]; - } - lua_pop(L, 1); - } - - if(luaW_tableget(L, -1, "use_old")) { - rule.use_old_ = luaW_toboolean(L, -1); - lua_pop(L, 1); - } - - if(luaW_tableget(L, -1, "replace_if_failed")) { - rule.replace_if_failed_ = luaW_toboolean(L, -1); - lua_pop(L, 1); - } - - lua_pop(L, 1); - } - lua_pop(L, 1); - } - } - - - gamemap mask_map(""); - mask_map.read(t_str, false); - board().map().overlay(mask_map, loc, rules, is_odd, ignore_special_locations); - - for(team& t : board().teams()) { - t.fix_villages(board().map()); - } - - if (game_display_) { - game_display_->needs_rebuild(true); - } - - return 0; -} - /** * Gets details about a terrain. * - Arg 1: terrain code string. @@ -1272,22 +1052,6 @@ int game_lua_kernel::intf_set_village_owner(lua_State *L) return 0; } - -/** - * Returns the map size. - * - Ret 1: width. - * - Ret 2: height. - * - Ret 3: border size. - */ -int game_lua_kernel::intf_get_map_size(lua_State *L) -{ - const gamemap &map = board().map(); - lua_pushinteger(L, map.w()); - lua_pushinteger(L, map.h()); - lua_pushinteger(L, map.border_size()); - return 3; -} - /** * Returns the currently overed tile. * - Ret 1: x. @@ -1468,6 +1232,10 @@ int game_lua_kernel::impl_current_get(lua_State *L) return_int_attrib("turn", play_controller_.turn()); return_string_attrib("synced_state", synced_state()); return_bool_attrib("user_can_invoke_commands", !play_controller_.is_lingering() && play_controller_.gamestate().init_side_done() && !events::commands_disabled && gamedata().phase() == game_data::PLAY); + + if(strcmp(m, "map") == 0) { + return intf_terrainmap_get(L); + } if (strcmp(m, "event_context") == 0) { @@ -4235,9 +4003,7 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports { "get_all_vars", &dispatch<&game_lua_kernel::intf_get_all_vars > }, { "get_end_level_data", &dispatch<&game_lua_kernel::intf_get_end_level_data > }, { "get_locations", &dispatch<&game_lua_kernel::intf_get_locations > }, - { "get_map_size", &dispatch<&game_lua_kernel::intf_get_map_size > }, { "get_sound_source", &dispatch<&game_lua_kernel::intf_get_sound_source > }, - { "get_terrain", &dispatch<&game_lua_kernel::intf_get_terrain > }, { "get_terrain_info", &dispatch<&game_lua_kernel::intf_get_terrain_info > }, { "get_time_of_day", &dispatch<&game_lua_kernel::intf_get_time_of_day > }, { "get_max_liminal_bonus", &dispatch<&game_lua_kernel::intf_get_max_liminal_bonus > }, @@ -4266,13 +4032,11 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports { "set_end_campaign_text", &dispatch<&game_lua_kernel::intf_set_end_campaign_text > }, { "create_side", &dispatch<&game_lua_kernel::intf_create_side > }, { "set_next_scenario", &dispatch<&game_lua_kernel::intf_set_next_scenario > }, - { "set_terrain", &dispatch<&game_lua_kernel::intf_set_terrain > }, { "set_variable", &dispatch<&game_lua_kernel::intf_set_variable > }, { "set_village_owner", &dispatch<&game_lua_kernel::intf_set_village_owner > }, { "simulate_combat", &dispatch<&game_lua_kernel::intf_simulate_combat > }, { "synchronize_choice", &intf_synchronize_choice }, { "synchronize_choices", &intf_synchronize_choices }, - { "terrain_mask", &dispatch<&game_lua_kernel::intf_terrain_mask > }, { "teleport", &dispatch<&game_lua_kernel::intf_teleport > }, { "place_shroud", &dispatch2<&game_lua_kernel::intf_shroud_op, true > }, { "remove_shroud", &dispatch2<&game_lua_kernel::intf_shroud_op, false > }, @@ -4317,6 +4081,9 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports // Create the unit_types table cmd_log_ << lua_unit_type::register_table(L); + // Create the unit_types table + cmd_log_ << lua_terrainmap::register_metatables(L, false); + // Create the ai elements table. cmd_log_ << "Adding ai elements table...\n"; @@ -4341,6 +4108,14 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports lua_pushcfunction(L, &lua_common::intf_tovconfig); lua_setfield(L, -2, "tovconfig"); lua_pop(L, 1); + + // Add replace_if_failed to the map module + luaW_getglobal(L, "wesnoth", "map"); + lua_pushcfunction(L, &intf_replace_if_failed); + lua_setfield(L, -2, "replace_if_failed"); + lua_pushcfunction(L, &intf_terrain_mask); + lua_setfield(L, -2, "terrain_mask"); + lua_pop(L, 1); // Create the units module cmd_log_ << "Adding units module...\n"; @@ -4480,8 +4255,6 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports lua_getglobal(L, "wesnoth"); lua_newtable(L); lua_setfield(L, -2, "game_events"); - push_locations_table(L); - lua_setfield(L, -2, "special_locations"); lua_pop(L, 1); // Create the theme_items table. diff --git a/src/scripting/game_lua_kernel.hpp b/src/scripting/game_lua_kernel.hpp index a043e6fc92e9b..6a26184723d09 100644 --- a/src/scripting/game_lua_kernel.hpp +++ b/src/scripting/game_lua_kernel.hpp @@ -92,9 +92,6 @@ class game_lua_kernel : public lua_kernel_base int intf_unit_ability(lua_State *L); int intf_view_locked(lua_State *L); int intf_lock_view(lua_State *L); - int intf_get_terrain(lua_State *L); - int intf_set_terrain(lua_State *L); - int intf_terrain_mask(lua_State *L); int intf_get_terrain_info(lua_State *L); int intf_get_time_of_day(lua_State *L); int intf_get_max_liminal_bonus(lua_State *L); diff --git a/src/scripting/lua_terrainfilter.cpp b/src/scripting/lua_terrainfilter.cpp index fd55311e52589..1e7876c38c0f9 100644 --- a/src/scripting/lua_terrainfilter.cpp +++ b/src/scripting/lua_terrainfilter.cpp @@ -199,7 +199,7 @@ class filter_impl { public: filter_impl() {}; - virtual bool matches(const mapgen_gamemap& m, map_location l) = 0; + virtual bool matches(const gamemap_base& m, map_location l) = 0; virtual ~filter_impl() {}; }; @@ -234,7 +234,7 @@ class and_filter : public con_filter LOG_LMG << "created and filter\n"; } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(and); for(const auto& pfilter : list_) { @@ -255,7 +255,7 @@ class or_filter : public con_filter LOG_LMG << "created or filter\n"; } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(or); for(const auto& pfilter : list_) { @@ -276,7 +276,7 @@ class nand_filter : public con_filter LOG_LMG << "created nand filter\n"; } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(nand); for(const auto& pfilter : list_) { @@ -297,7 +297,7 @@ class nor_filter : public con_filter LOG_LMG << "created nor filter\n"; } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(nor); for(const auto& pfilter : list_) { @@ -322,7 +322,7 @@ class cached_filter : public filter_impl lua_pop(L, 1); } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(cached); int cache_size = 2 * m.total_width() * m.total_height(); @@ -357,7 +357,7 @@ class x_filter : public filter_impl filter_ = parse_range(luaW_tostring(L, -1)); lua_pop(L, 1); } - bool matches(const mapgen_gamemap&, map_location l) override + bool matches(const gamemap_base&, map_location l) override { LOG_MATCHES(x); return l.x >= 0 && l.x < int(filter_.size()) && filter_[l.x]; @@ -377,7 +377,7 @@ class y_filter : public filter_impl lua_pop(L, 1); } - bool matches(const mapgen_gamemap&, map_location l) override + bool matches(const gamemap_base&, map_location l) override { LOG_MATCHES(y); return l.y >= 0 && l.y < int(filter_.size()) && filter_[l.y]; @@ -394,10 +394,10 @@ class onborder_filter : public filter_impl LOG_LMG << "creating onborder filter\n"; } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(onborder); - return !m.on_map_noborder(l); + return !m.on_board(l); } }; @@ -414,10 +414,10 @@ class terrain_filter : public filter_impl lua_pop(L, 1); } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(terrain); - const t_translation::terrain_code letter = m[l]; + const t_translation::terrain_code letter = m.get_terrain(l); return t_translation::terrain_matches(letter, filter_); } @@ -451,7 +451,7 @@ class adjacent_filter : public filter_impl lua_pop(L, 1); } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(adjacent); int count = 0; @@ -459,7 +459,7 @@ class adjacent_filter : public filter_impl offset_list_t& offsets = (l.wml_x() & 1) ? odd_offsets_ : even_offsets_; for(const auto& offset : offsets) { map_location ad = {l.x + offset.first, l.y + offset.second}; - if(m.on_map(ad) && filter_->matches(m, ad)) { + if(m.on_board_with_border(ad) && filter_->matches(m, ad)) { if(accepted_counts_.size() == 0) { return true; } @@ -496,7 +496,7 @@ class findin_filter : public filter_impl } set_ = &insert_res.first->second; } - bool matches(const mapgen_gamemap&, map_location l) override + bool matches(const gamemap_base&, map_location l) override { LOG_MATCHES(findin); if(set_) { @@ -529,14 +529,14 @@ class radius_filter : public filter_impl lua_pop(L, 1); } - bool matches(const mapgen_gamemap& m, map_location l) override + bool matches(const gamemap_base& m, map_location l) override { LOG_MATCHES(radius); std::set result; get_tiles_radius({{ l }}, radius_, result, [&](const map_location& l) { - return m.on_map(l); + return m.on_board_with_border(l); }, [&](const map_location& l) { return !filter_radius_ || filter_radius_->matches(m, l); @@ -573,7 +573,7 @@ class formula_filter : public filter_impl ERR_LMG << "formula error" << e.what() << "\n"; } } - bool matches(const mapgen_gamemap&, map_location l) override + bool matches(const gamemap_base&, map_location l) override { LOG_MATCHES(formula); try { @@ -672,7 +672,7 @@ filter::filter(lua_State* L, int data_index, int res_index) LOG_LMG << "finished creating filter object\n"; } -bool filter::matches(const mapgen_gamemap& m, map_location l) +bool filter::matches(const gamemap_base& m, map_location l) { log_scope("filter::matches"); return impl_->matches(m, l); @@ -685,7 +685,7 @@ filter::~filter() } -static int intf_mg_get_locations_part2(lua_State* L, mapgen_gamemap& m, lua_mapgen::filter& f) +static int intf_mg_get_locations_part2(lua_State* L, gamemap_base& m, lua_mapgen::filter& f) { location_set res; LOG_LMG << "map:get_locations vaidargs\n"; @@ -717,7 +717,7 @@ int intf_mg_get_locations(lua_State* L) { //todo: create filter form table if needed LOG_LMG << "map:get_locations\n"; - mapgen_gamemap& m = luaW_checkterrainmap(L, 1); + gamemap_base& m = luaW_checkterrainmap(L, 1); if(luaW_is_mgfilter(L, 2)) { lua_mapgen::filter& f = luaW_check_mgfilter(L, 2); return intf_mg_get_locations_part2(L, m, f); @@ -733,14 +733,14 @@ int intf_mg_get_locations(lua_State* L) int intf_mg_get_tiles_radius(lua_State* L) { - mapgen_gamemap& m = luaW_checkterrainmap(L, 1); + gamemap_base& m = luaW_checkterrainmap(L, 1); lua_mapgen::filter& f = luaW_check_mgfilter(L, 3); location_set s = luaW_to_locationset(L, 2); location_set res; int r = luaL_checkinteger(L, 4); get_tiles_radius(std::move(s), r, res, [&](const map_location& l) { - return m.on_map(l); + return m.on_board_with_border(l); }, [&](const map_location& l) { return f.matches(m, l); diff --git a/src/scripting/lua_terrainfilter.hpp b/src/scripting/lua_terrainfilter.hpp index d42dfd5b7dda1..4f0f38687afef 100644 --- a/src/scripting/lua_terrainfilter.hpp +++ b/src/scripting/lua_terrainfilter.hpp @@ -18,6 +18,7 @@ #include #include #include +#include "map/map.hpp" #include "scripting/lua_common.hpp" struct lua_State; @@ -45,7 +46,7 @@ namespace lua_mapgen ~filter(); - bool matches(const mapgen_gamemap& m, map_location l); + bool matches(const gamemap_base& m, map_location l); //todo: add a clear cache function. private: std::map> known_sets_; diff --git a/src/scripting/lua_terrainmap.cpp b/src/scripting/lua_terrainmap.cpp index 804e21128d3b7..782612e8386ce 100644 --- a/src/scripting/lua_terrainmap.cpp +++ b/src/scripting/lua_terrainmap.cpp @@ -22,6 +22,9 @@ #include "scripting/lua_common.hpp" #include "scripting/push_check.hpp" #include "scripting/game_lua_kernel.hpp" +#include "resources.hpp" +#include "game_board.hpp" +#include "play_controller.hpp" #include "lua/lauxlib.h" #include "lua/lua.h" @@ -30,77 +33,26 @@ static lg::log_domain log_scripting_lua("scripting/lua"); #define LOG_LUA LOG_STREAM(info, log_scripting_lua) #define ERR_LUA LOG_STREAM(err, log_scripting_lua) -static const char terrainmapKey[] = "terrainmap"; -static const char maplocationKey[] = "special_locations"; +static const char terrainmapKey[] = "terrain map"; +static const char maplocationKey[] = "special locations"; +static const char mapReplaceIfFailedKey[] = "replace_if_failed terrain code"; -using std::string_view; - -//////// SPECIAL LOCATION //////// - -bool luaW_isslocs(lua_State* L, int index) -{ - return luaL_testudata(L, index, maplocationKey) != nullptr; -} - - -mapgen_gamemap* luaW_toslocs(lua_State *L, int index) -{ - if(!lua_istable(L, index)) { - return nullptr; - } - - lua_rawgeti(L, index, 1); - mapgen_gamemap* m = luaW_toterrainmap(L, -1); - lua_pop(L, 1); - return m; +namespace replace_if_failed_idx { + enum {CODE = 1, MODE = 2}; } -mapgen_gamemap& luaW_check_slocs(lua_State *L, int index) -{ - if(mapgen_gamemap* m = luaW_toslocs(L, index)) { - return *m; - } - luaW_type_error(L, index, "terrainmap"); - throw "luaW_type_error didn't thow."; -} +using std::string_view; -void lua_slocs_setmetatable(lua_State *L) -{ - luaL_setmetatable(L, maplocationKey); -} -/** - * @a index the index of the map object. - */ -void luaW_pushslocs(lua_State *L, int index) -{ - lua_pushvalue(L, index); - //stack: map - lua_createtable(L, 1, 0); - //stack: map, slocs - lua_pushvalue(L, -2); - //stack: map, slocs, map - lua_rawseti(L, -2, 1); - //stack: map, slocs - luaL_setmetatable(L, maplocationKey); - //stack: map, slocs - lua_remove(L, -2); - //stack: slocs -} +//////// SPECIAL LOCATION //////// int impl_slocs_get(lua_State* L) { - //todo: calling map.special_locations[1] will return the underlying map - // object instead of the first starting position, because the lua - // special locations is actually a table with the map object at - // index 1. The probably easiest way to fix this inconsistency is - // to just disallow all integer indices here. - mapgen_gamemap& m = luaW_check_slocs(L, 1); + gamemap_base& m = luaW_checkterrainmap(L, 1); string_view id = luaL_checkstring(L, 2); auto res = m.special_location(std::string(id)); - if(res.wml_x() >= 0) { + if(res.valid()) { luaW_pushlocation(L, res); - } - else { + } else { //functions with variable return numbers have been causing problem in the past lua_pushnil(L); } @@ -109,7 +61,7 @@ int impl_slocs_get(lua_State* L) int impl_slocs_set(lua_State* L) { - mapgen_gamemap& m = luaW_check_slocs(L, 1); + gamemap_base& m = luaW_checkterrainmap(L, 1); string_view id = luaL_checkstring(L, 2); map_location loc = luaW_checklocation(L, 3); @@ -117,119 +69,143 @@ int impl_slocs_set(lua_State* L) return 0; } +int impl_slocs_next(lua_State *L) +{ + gamemap_base& m = luaW_checkterrainmap(L, lua_upvalueindex(1)); + const t_translation::starting_positions::left_map& left = m.special_locations().left; + + t_translation::starting_positions::left_const_iterator it; + if (lua_isnoneornil(L, 2)) { + it = left.begin(); + } + else { + it = left.find(luaL_checkstring(L, 2)); + if (it == left.end()) { + return 0; + } + ++it; + } + if (it == left.end()) { + return 0; + } + lua_pushstring(L, it->first.c_str()); + luaW_pushlocation(L, it->second); + return 2; +} + +int impl_slocs_iter(lua_State *L) +{ + lua_settop(L, 1); + lua_pushvalue(L, 1); + lua_pushcclosure(L, &impl_slocs_next, 1); + lua_pushvalue(L, 1); + lua_pushnil(L); + return 3; +} + //////// MAP //////// mapgen_gamemap::mapgen_gamemap(std::string_view s) - : tiles_() - , starting_positions_() { if(s.empty()) { return; } //throws t_translation::error //todo: make read_game_map take a string_view - tiles_ = t_translation::read_game_map(s, starting_positions_, t_translation::coordinate{ 1, 1 }); + tiles() = t_translation::read_game_map(s, special_locations(), t_translation::coordinate{ 1, 1 }); } mapgen_gamemap::mapgen_gamemap(int w, int h, terrain_code t) - : tiles_(w, h, t) - , starting_positions_() + : gamemap_base(w, h, t) { } -std::string mapgen_gamemap::to_string() const +// This can produce invalid combinations in rare case +// where an overlay doesn't have an independent terrain definition, +// or if you set an overlay with no base and merge mode other than OVERLAY. +void simplemerge(t_translation::terrain_code old_t, t_translation::terrain_code& new_t, const terrain_type_data::merge_mode mode) { - return t_translation::write_game_map(tiles_, starting_positions_, { 1, 1 }) + "\n"; + switch(mode) { + case terrain_type_data::OVERLAY: + new_t = t_translation::terrain_code(old_t.base, new_t.overlay); + break; + case terrain_type_data::BASE: + new_t = t_translation::terrain_code(new_t.base, old_t.overlay); + break; + case terrain_type_data::BOTH: + new_t = t_translation::terrain_code(new_t.base, new_t.overlay); + break; + } } -void mapgen_gamemap::set_terrain(const map_location& loc, const terrain_code & terrain, const terrain_type_data::merge_mode mode) +void mapgen_gamemap::set_terrain(const map_location& loc, const terrain_code & terrain, const terrain_type_data::merge_mode mode, bool) { - terrain_code& t = (*this)[loc]; - terrain_code old = t; - t = terrain; + terrain_code old = get_terrain(loc); + terrain_code t = terrain; simplemerge(old, t, mode); - + tiles().get(loc.x + border_size(), loc.y + border_size()) = t; } -void mapgen_gamemap::simplemerge(terrain_code old_t, terrain_code& new_t, const terrain_type_data::merge_mode mode) -{ - if(mode == terrain_type_data::OVERLAY) { - new_t = t_translation::terrain_code(old_t.base, new_t.overlay); - } - if(mode == terrain_type_data::BASE) { - new_t = t_translation::terrain_code(new_t.base, old_t.overlay); - } -} - -void mapgen_gamemap::set_special_location(const std::string& id, const map_location& loc) -{ - bool valid = loc.valid(); - auto it_left = starting_positions_.left.find(id); - if (it_left != starting_positions_.left.end()) { - if (valid) { - starting_positions_.left.replace_data(it_left, loc); - } - else { - starting_positions_.left.erase(it_left); - } +struct lua_map_ref { + virtual gamemap_base& get_map() = 0; + virtual ~lua_map_ref() {} +}; + +// Mapgen map reference, owned by Lua +struct lua_map_ref_gen : public lua_map_ref { + mapgen_gamemap map; + template + lua_map_ref_gen(T&&... params) : map(std::forward(params)...) {} + gamemap_base& get_map() override { + return map; } - else { - starting_positions_.left.insert(it_left, std::pair(id, loc)); +}; + +// Main map reference, owned by the engine +struct lua_map_ref_main : public lua_map_ref { + gamemap& map; + lua_map_ref_main(gamemap& ref) : map(ref) {} + gamemap_base& get_map() override { + return map; } -} - -map_location mapgen_gamemap::special_location(const std::string& id) const -{ - auto it = starting_positions_.left.find(id); - if (it != starting_positions_.left.end()) { - auto& coordinate = it->second; - return map_location(coordinate.x, coordinate.y); +}; + +// Non-owning map reference to either type (used for special location userdata) +struct lua_map_ref_locs : public lua_map_ref { + gamemap_base& map; + lua_map_ref_locs(gamemap_base& ref) : map(ref) {} + gamemap_base& get_map() override { + return map; } - else { - return map_location(); - } -} +}; bool luaW_isterrainmap(lua_State* L, int index) { - return luaL_testudata(L, index, terrainmapKey) != nullptr; + return luaL_testudata(L, index, terrainmapKey) != nullptr || luaL_testudata(L, index, maplocationKey) != nullptr; } -mapgen_gamemap* luaW_toterrainmap(lua_State *L, int index) +gamemap_base* luaW_toterrainmap(lua_State *L, int index) { if(luaW_isterrainmap(L, index)) { - return static_cast(lua_touserdata(L, index)); + return &static_cast(lua_touserdata(L, index))->get_map(); } return nullptr; } -mapgen_gamemap& luaW_checkterrainmap(lua_State *L, int index) +gamemap_base& luaW_checkterrainmap(lua_State *L, int index) { if(luaW_isterrainmap(L, index)) { - return *static_cast(lua_touserdata(L, index)); + return static_cast(lua_touserdata(L, index))->get_map(); } luaW_type_error(L, index, "terrainmap"); throw "luaW_type_error didn't throw"; } -void lua_terrainmap_setmetatable(lua_State *L) -{ - luaL_setmetatable(L, terrainmapKey); -} - -template -mapgen_gamemap* luaW_pushmap(lua_State *L, T&&... params) -{ - mapgen_gamemap* res = new(L) mapgen_gamemap(std::forward(params)...); - lua_terrainmap_setmetatable(L); - return res; -} - /** * Create a map. - * - Arg 1: string describing the map data. + * - Arg 1: string descripbing the map data. * - or: * - Arg 1: int, width * - Arg 2: int, height @@ -241,14 +217,20 @@ int intf_terrainmap_create(lua_State *L) int w = lua_tonumber(L, 1); int h = lua_tonumber(L, 2); auto terrain = t_translation::read_terrain_code(luaL_checkstring(L, 3)); - luaW_pushmap(L, w, h, terrain); - return 1; - } - else { + new(L) lua_map_ref_gen(w, h, terrain); + } else { string_view data_str = luaL_checkstring(L, 1); - luaW_pushmap(L, data_str); - return 1; + new(L) lua_map_ref_gen(data_str); } + luaL_setmetatable(L, terrainmapKey); + return 1; +} + +int intf_terrainmap_get(lua_State* L) +{ + new(L) lua_map_ref_main(const_cast(resources::gameboard->map())); + luaL_setmetatable(L, terrainmapKey); + return 1; } /** @@ -256,11 +238,54 @@ int intf_terrainmap_create(lua_State *L) */ static int impl_terrainmap_collect(lua_State *L) { - mapgen_gamemap *u = static_cast(lua_touserdata(L, 1)); - u->mapgen_gamemap::~mapgen_gamemap(); + lua_map_ref* m = static_cast(lua_touserdata(L, 1)); + m->lua_map_ref::~lua_map_ref(); return 0; } +static void luaW_push_terrain(lua_State* L, gamemap_base& map, map_location loc) +{ + auto t = map.get_terrain(loc); + lua_pushstring(L, t_translation::write_terrain_code(t).c_str()); +} + +static void impl_merge_terrain(lua_State* L, gamemap_base& map, map_location loc) +{ + auto mode = terrain_type_data::BOTH; + bool replace_if_failed = false; + string_view t_str; + if(luaL_testudata(L, 3, mapReplaceIfFailedKey)) { + replace_if_failed = true; + lua_getiuservalue(L, 3, replace_if_failed_idx::CODE); + t_str = luaL_checkstring(L, -1); + lua_getiuservalue(L, 3, replace_if_failed_idx::MODE); + mode = terrain_type_data::merge_mode(luaL_checkinteger(L, -1)); + } else { + t_str = luaL_checkstring(L, 3); + if(t_str.front() == '^') { + mode = terrain_type_data::OVERLAY; + } else if(t_str.back() == '^') { + mode = terrain_type_data::BASE; + } + } + + auto ter = t_translation::read_terrain_code(t_str); + + if(auto gm = dynamic_cast(&map)) { + if(resources::gameboard) { + bool result = resources::gameboard->change_terrain(loc, ter, mode, replace_if_failed); + + for(team& t : resources::gameboard->teams()) { + t.fix_villages(*gm); + } + + if(resources::controller) { + resources::controller->get_display().needs_rebuild(result); + } + } + } else map.set_terrain(loc, ter, mode, replace_if_failed); +} + /** * Gets some data on a map (__index metamethod). * - Arg 1: full userdata containing the map. @@ -269,19 +294,29 @@ static int impl_terrainmap_collect(lua_State *L) */ static int impl_terrainmap_get(lua_State *L) { - mapgen_gamemap& tm = luaW_checkterrainmap(L, 1); + gamemap_base& tm = luaW_checkterrainmap(L, 1); + map_location loc; + if(luaW_tolocation(L, 2, loc)) { + luaW_push_terrain(L, tm, loc); + return 1; + } + char const *m = luaL_checkstring(L, 2); // Find the corresponding attribute. return_int_attrib("width", tm.total_width()); return_int_attrib("height", tm.total_height()); + return_int_attrib("playable_width", tm.w()); + return_int_attrib("playable_height", tm.h()); + return_int_attrib("border_size", tm.border_size()); return_string_attrib("data", tm.to_string()); if(strcmp(m, "special_locations") == 0) { - luaW_pushslocs(L, 1); + new(L) lua_map_ref_locs(tm); + luaL_setmetatable(L, maplocationKey); return 1; } - if(luaW_getmetafield(L, 1, m)) { + if(luaW_getglobal(L, "wesnoth", "map", m)) { return 1; } return 0; @@ -295,59 +330,21 @@ static int impl_terrainmap_get(lua_State *L) */ static int impl_terrainmap_set(lua_State *L) { - mapgen_gamemap& tm = luaW_checkterrainmap(L, 1); - UNUSED(tm); + gamemap_base& tm = luaW_checkterrainmap(L, 1); + map_location loc; + // The extra check that value (arg 3) isn't a number is because without it, + // map[4] = 5 would be interpreted as map[{4, 5}] = nil, due to the way + // luaW_tolocation modifies the stack if it finds a pair of numbers on it. + if(lua_type(L, 3) != LUA_TNUMBER && luaW_tolocation(L, 2, loc)) { + impl_merge_terrain(L, tm, loc); + return 0; + } char const *m = luaL_checkstring(L, 2); std::string err_msg = "unknown modifiable property of map: "; err_msg += m; return luaL_argerror(L, 2, err_msg.c_str()); } - -/** - * Sets a terrain code. - * - Arg 1: map location. - * - Arg 2: terrain code string. - * - Arg 3: layer: (overlay|base|both, default=both) -*/ -static int intf_set_terrain(lua_State *L) -{ - mapgen_gamemap& tm = luaW_checkterrainmap(L, 1); - map_location loc = luaW_checklocation(L, 2); - string_view t_str = luaL_checkstring(L, 3); - - auto terrain = t_translation::read_terrain_code(t_str); - auto mode = terrain_type_data::BOTH; - - if(!lua_isnoneornil(L, 4)) { - string_view mode_str = luaL_checkstring(L, 4); - if(mode_str == "base") { - mode = terrain_type_data::BASE; - } - else if(mode_str == "overlay") { - mode = terrain_type_data::OVERLAY; - } - } - - tm.set_terrain(loc, terrain, mode); - return 0; -} - -/** - * Gets a terrain code. - * - Arg 1: map location. - * - Ret 1: string. - */ -static int intf_get_terrain(lua_State *L) -{ - mapgen_gamemap& tm = luaW_checkterrainmap(L, 1); - map_location loc = luaW_checklocation(L, 2); - - auto t = tm[loc]; - lua_pushstring(L, t_translation::write_terrain_code(t).c_str()); - return 1; -} - static std::vector read_rules_vector(lua_State *L, int index) { std::vector rules; @@ -363,6 +360,7 @@ static std::vector read_rules_vector(lua_State *L, int in rule.old_ = t_translation::read_list(luaW_tostring(L, -1)); lua_pop(L, 1); } + if(luaW_tableget(L, -1, "new")) { rule.new_ = t_translation::read_list(luaW_tostring(L, -1)); lua_pop(L, 1); @@ -405,19 +403,18 @@ static std::vector read_rules_vector(lua_State *L, int in * - ignore_special_locations: boolean * - rules: table of tables */ -int mapgen_gamemap::intf_mg_terrain_mask(lua_State *L) +int intf_terrain_mask(lua_State *L) { - mapgen_gamemap& tm1 = luaW_checkterrainmap(L, 1); + gamemap_base& map = luaW_checkterrainmap(L, 1); map_location loc = luaW_checklocation(L, 2); - mapgen_gamemap& tm2 = luaW_checkterrainmap(L, 3); bool is_odd = false; bool ignore_special_locations = false; std::vector rules; if(lua_istable(L, 4)) { - is_odd = luaW_table_get_def(L, 4, "is_odd", false); - ignore_special_locations = luaW_table_get_def(L, 4, "ignore_special_locations", false); + is_odd = luaW_table_get_def(L, 4, "is_odd", false); + ignore_special_locations = luaW_table_get_def(L, 4, "ignore_special_locations", false); if(luaW_tableget(L, 4, "rules")) { if(!lua_istable(L, -1)) { @@ -427,28 +424,78 @@ int mapgen_gamemap::intf_mg_terrain_mask(lua_State *L) lua_pop(L, 1); } } + + if(lua_isstring(L, 3)) { + const std::string t_str = luaL_checkstring(L, 2); + std::unique_ptr mask; + if(auto gmap = dynamic_cast(&map)) { + auto mask_ptr = new gamemap(""); + mask_ptr->read(t_str, false); + mask.reset(mask_ptr); + } else { + mask.reset(new mapgen_gamemap(t_str)); + } + map.overlay(*mask, loc, rules, is_odd, ignore_special_locations); + } else { + gamemap_base& mask = luaW_checkterrainmap(L, 3); + map.overlay(mask, loc, rules, is_odd, ignore_special_locations); + } + + if(resources::gameboard) { + if(auto gmap = dynamic_cast(&map)) { + for(team& t : resources::gameboard->teams()) { + t.fix_villages(*gmap); + } + } + } - gamemap::overlay_impl( - tm1.tiles_, - tm1.starting_positions_, - tm2.tiles_, - tm2.starting_positions_, - [&](const map_location& loc, const t_translation::terrain_code& t, terrain_type_data::merge_mode mode, bool) { tm1.set_terrain(loc, t, mode); }, - loc, - rules, - is_odd, - ignore_special_locations - ); + if(resources::controller) { + resources::controller->get_display().needs_rebuild(true); + } return 0; } +int intf_replace_if_failed(lua_State* L) +{ + auto mode = terrain_type_data::BOTH; + if(!lua_isnoneornil(L, 2)) { + string_view mode_str = luaL_checkstring(L, 2); + if(mode_str == "base") { + mode = terrain_type_data::BASE; + } else if(mode_str == "overlay") { + mode = terrain_type_data::OVERLAY; + } else if(mode_str != "both") { + return luaL_argerror(L, 2, "must be one of 'base', 'overlay', or 'both'"); + } + } + + lua_newuserdatauv(L, 0, 2); + lua_pushinteger(L, int(mode)); + lua_setiuservalue(L, -2, replace_if_failed_idx::MODE); + lua_pushvalue(L, 1); + lua_setiuservalue(L, -2, replace_if_failed_idx::CODE); + luaL_setmetatable(L, mapReplaceIfFailedKey); + return 1; +} + +static int impl_replace_if_failed_tostring(lua_State* L) +{ + static const char* mode_strs[] = {"base", "overlay", "both"}; + lua_getiuservalue(L, 1, replace_if_failed_idx::CODE); + string_view t_str = luaL_checkstring(L, -1); + lua_getiuservalue(L, 1, replace_if_failed_idx::MODE); + int mode = luaL_checkinteger(L, -1); + lua_pushfstring(L, "replace_if_failed('%s', '%s')", t_str.data(), mode_strs[mode]); + return 1; +} + namespace lua_terrainmap { - std::string register_metatables(lua_State* L) + std::string register_metatables(lua_State* L, bool use_tf) { std::ostringstream cmd_out; - cmd_out << "Adding terrainmap metatable...\n"; + cmd_out << "Adding terrain map metatable...\n"; luaL_newmetatable(L, terrainmapKey); lua_pushcfunction(L, impl_terrainmap_collect); @@ -457,28 +504,32 @@ namespace lua_terrainmap { lua_setfield(L, -2, "__index"); lua_pushcfunction(L, impl_terrainmap_set); lua_setfield(L, -2, "__newindex"); - lua_pushstring(L, "terrainmap"); + lua_pushstring(L, terrainmapKey); lua_setfield(L, -2, "__metatable"); // terrainmap methods - lua_pushcfunction(L, intf_set_terrain); - lua_setfield(L, -2, "set_terrain"); - lua_pushcfunction(L, intf_get_terrain); - lua_setfield(L, -2, "get_terrain"); - lua_pushcfunction(L, intf_mg_get_locations); - lua_setfield(L, -2, "get_locations"); - lua_pushcfunction(L, intf_mg_get_tiles_radius); - lua_setfield(L, -2, "get_tiles_radius"); - lua_pushcfunction(L, &mapgen_gamemap::intf_mg_terrain_mask); - lua_setfield(L, -2, "terrain_mask"); - - cmd_out << "Adding terrainmap2 metatable...\n"; + if(use_tf) { + lua_pushcfunction(L, intf_mg_get_locations); + lua_setfield(L, -2, "get_locations"); + lua_pushcfunction(L, intf_mg_get_tiles_radius); + lua_setfield(L, -2, "get_tiles_radius"); + } + + luaL_newmetatable(L, mapReplaceIfFailedKey); + lua_pushcfunction(L, impl_replace_if_failed_tostring); + lua_setfield(L, -2, "__tostring"); + lua_pushstring(L, mapReplaceIfFailedKey); + lua_setfield(L, -2, "__metatable"); + + cmd_out << "Adding special locations metatable...\n"; luaL_newmetatable(L, maplocationKey); lua_pushcfunction(L, impl_slocs_get); lua_setfield(L, -2, "__index"); lua_pushcfunction(L, impl_slocs_set); lua_setfield(L, -2, "__newindex"); - lua_pushstring(L, "special_locations"); + lua_pushcfunction(L, impl_slocs_iter); + lua_setfield(L, -2, "__pairs"); + lua_pushstring(L, maplocationKey); lua_setfield(L, -2, "__metatable"); return cmd_out.str(); diff --git a/src/scripting/lua_terrainmap.hpp b/src/scripting/lua_terrainmap.hpp index 89cb3a6033ab6..a559af48c1331 100644 --- a/src/scripting/lua_terrainmap.hpp +++ b/src/scripting/lua_terrainmap.hpp @@ -17,6 +17,7 @@ #include #include "scripting/lua_common.hpp" #include "map/location.hpp" +#include "map/map.hpp" #include "terrain/translation.hpp" #include "terrain/type_data.hpp" @@ -24,60 +25,13 @@ struct lua_State; class lua_unit; struct map_location; -// this clas is similar to the orginal gamemap class but they have no 'is' rlation: -// mapgen_gamemap, unlike gamemap offers 'raw' access to the data -// gamemap, unlike mapgen_gamemap uses terrain type data. -class mapgen_gamemap -{ +// Unlike the original gamemap, this offers 'raw' access to the data. +// The original gamemap uses terrain type data. +class mapgen_gamemap : public gamemap_base { public: - using terrain_code = t_translation::terrain_code; - using terrain_map = t_translation::ter_map; - using starting_positions = t_translation::starting_positions; explicit mapgen_gamemap(std::string_view data); mapgen_gamemap(int w, int h, terrain_code); - - std::string to_string() const; - - /** Effective map width. */ - int w() const { return total_width() - 2; } - - /** Effective map height. */ - int h() const { return total_height() - 2; } - - /** Real width of the map, including borders. */ - int total_width() const { return tiles_.w; } - - /** Real height of the map, including borders */ - int total_height() const { return tiles_.h; } - - bool on_map(const map_location& loc) const - { - return loc.wml_x() >= 0 && loc.wml_x() < total_width() && loc.wml_y() >= 0 && loc.wml_y() < total_height(); - } - - bool on_map_noborder(const map_location& loc) const - { - return loc.wml_x() > 0 && loc.wml_x() < total_width() - 1 && loc.wml_y() > 0 && loc.wml_y() < total_height() - 1; - } - - terrain_code& operator[](const map_location& loc) - { - return tiles_.get(loc.wml_x(), loc.wml_y()); - } - - const terrain_code& operator[](const map_location& loc) const - { - return tiles_.get(loc.wml_x(), loc.wml_y()); - } - - void set_terrain(const map_location& loc, const terrain_code & terrain, const terrain_type_data::merge_mode mode); - static void simplemerge(terrain_code old, terrain_code& t, const terrain_type_data::merge_mode mode); - - starting_positions& special_locations() { return starting_positions_; } - const starting_positions& special_locations() const { return starting_positions_; } - - void set_special_location(const std::string& id, const map_location& loc); - map_location special_location(const std::string& id) const; + void set_terrain(const map_location& loc, const terrain_code & terrain, const terrain_type_data::merge_mode mode = terrain_type_data::BOTH, bool replace_if_failed = false) override; template void for_each_loc(const F& f) const @@ -88,38 +42,23 @@ class mapgen_gamemap } } } - static int intf_mg_terrain_mask(lua_State *L); -private: - t_translation::ter_map tiles_; - starting_positions starting_positions_; }; -bool luaW_isslocs(lua_State* L, int index); - -mapgen_gamemap* luaW_toslocs(lua_State *L, int index); - -mapgen_gamemap& luaW_check_slocs(lua_State *L, int index); - -void lua_slocs_setmetatable(lua_State *L); - -void luaW_pushslocs(lua_State *L, int index); - -int impl_slocs_get(lua_State* L); - -int impl_slocs_set(lua_State* L); +int intf_terrain_mask(lua_State *L); bool luaW_isterrainmap(lua_State* L, int index); -mapgen_gamemap* luaW_toterrainmap(lua_State *L, int index); +gamemap_base* luaW_toterrainmap(lua_State *L, int index); -mapgen_gamemap& luaW_checkterrainmap(lua_State *L, int index); +gamemap_base& luaW_checkterrainmap(lua_State *L, int index); void lua_terrainmap_setmetatable(lua_State *L); -mapgen_gamemap* luaW_pushmap(lua_State *L, mapgen_gamemap&& u); - int intf_terrainmap_create(lua_State *L); +int intf_terrainmap_get(lua_State *L); + +int intf_replace_if_failed(lua_State* L); namespace lua_terrainmap { - std::string register_metatables(lua_State *L); + std::string register_metatables(lua_State *L, bool use_tf); } diff --git a/src/scripting/mapgen_lua_kernel.cpp b/src/scripting/mapgen_lua_kernel.cpp index 87daaba75600f..5b03a0ca91464 100644 --- a/src/scripting/mapgen_lua_kernel.cpp +++ b/src/scripting/mapgen_lua_kernel.cpp @@ -237,7 +237,7 @@ mapgen_lua_kernel::mapgen_lua_kernel(const config* vars) assert(lua_gettop(L) == 0); - cmd_log_ << lua_terrainmap::register_metatables(L); + cmd_log_ << lua_terrainmap::register_metatables(L, true); cmd_log_ << lua_terrainfilter::register_metatables(L); }