← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/editor-resize-map into lp:widelands

 

Benedikt Straub has proposed merging lp:~widelands-dev/widelands/editor-resize-map into lp:widelands.

Commit message:
Add an editor tool to change the map size

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1218373 in widelands: "Feature to change map size (x / y) of currently open map"
  https://bugs.launchpad.net/widelands/+bug/1218373

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/editor-resize-map/+merge/365638

This is for b21.

Usage:
· Tool Menu → Resize
· Set the new map size
· Click on the map field where to split the map
New rows/columns will be inserted at the clicked spot. The field itself will then be located on the southeastern corner of the inserted rectangle of new fields. When making the map smaller, rows/columns south and east of the clicked field will be deleted.

-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/editor-resize-map into lp:widelands.
=== added file 'data/images/wui/editor/editor_menu_tool_resize.png'
Binary files data/images/wui/editor/editor_menu_tool_resize.png	1970-01-01 00:00:00 +0000 and data/images/wui/editor/editor_menu_tool_resize.png	2019-04-07 17:11:51 +0000 differ
=== added file 'data/images/wui/editor/fsel_editor_resize.png'
Binary files data/images/wui/editor/fsel_editor_resize.png	1970-01-01 00:00:00 +0000 and data/images/wui/editor/fsel_editor_resize.png	2019-04-07 17:11:51 +0000 differ
=== modified file 'src/editor/CMakeLists.txt'
--- src/editor/CMakeLists.txt	2018-11-04 17:56:37 +0000
+++ src/editor/CMakeLists.txt	2019-04-07 17:11:51 +0000
@@ -30,6 +30,8 @@
     tools/place_critter_tool.h
     tools/place_immovable_tool.cc
     tools/place_immovable_tool.h
+    tools/resize_tool.cc
+    tools/resize_tool.h
     tools/set_height_tool.cc
     tools/set_height_tool.h
     tools/set_origin_tool.cc
@@ -79,6 +81,8 @@
     ui_menus/tool_place_critter_options_menu.h
     ui_menus/tool_place_immovable_options_menu.cc
     ui_menus/tool_place_immovable_options_menu.h
+    ui_menus/tool_resize_options_menu.cc
+    ui_menus/tool_resize_options_menu.h
     ui_menus/tool_set_terrain_options_menu.cc
     ui_menus/tool_set_terrain_options_menu.h
     ui_menus/toolsize_menu.cc

=== modified file 'src/editor/editorinteractive.cc'
--- src/editor/editorinteractive.cc	2019-02-27 17:19:00 +0000
+++ src/editor/editorinteractive.cc	2019-04-07 17:11:51 +0000
@@ -72,7 +72,7 @@
      is_painting_(false),
      undo_(nullptr),
      redo_(nullptr),
-     tools_(new Tools()),
+     tools_(new Tools(e.map())),
      history_(nullptr)  // history needs the undo/redo buttons
 {
 	add_toolbar_button("wui/menus/menu_toggle_menu", "menu", _("Main menu"), &mainmenu_, true);
@@ -652,7 +652,7 @@
 		undo_->set_enabled(false);
 		redo_->set_enabled(false);
 
-		tools_.reset(new Tools());
+		tools_.reset(new Tools(egbase().map()));
 		select_tool(tools_->info, EditorTool::First);
 		set_sel_radius(0);
 

=== modified file 'src/editor/editorinteractive.h'
--- src/editor/editorinteractive.h	2019-02-23 11:00:49 +0000
+++ src/editor/editorinteractive.h	2019-04-07 17:11:51 +0000
@@ -29,6 +29,7 @@
 #include "editor/tools/noise_height_tool.h"
 #include "editor/tools/place_critter_tool.h"
 #include "editor/tools/place_immovable_tool.h"
+#include "editor/tools/resize_tool.h"
 #include "editor/tools/set_origin_tool.h"
 #include "editor/tools/set_port_space_tool.h"
 #include "editor/tools/set_starting_pos_tool.h"
@@ -49,7 +50,7 @@
 class EditorInteractive : public InteractiveBase {
 public:
 	struct Tools {
-		Tools()
+		Tools(const Widelands::Map& map)
 		   : current_pointer(&info),
 		     use_tool(EditorTool::First),
 		     increase_height(decrease_height, set_height),
@@ -58,7 +59,8 @@
 		     place_critter(delete_critter),
 		     increase_resources(decrease_resources, set_resources),
 		     set_port_space(unset_port_space),
-		     set_origin() {
+		     set_origin(),
+		     resize(map.get_width(), map.get_height()) {
 		}
 		EditorTool& current() const {
 			return *current_pointer;
@@ -83,6 +85,7 @@
 		EditorSetPortSpaceTool set_port_space;
 		EditorUnsetPortSpaceTool unset_port_space;
 		EditorSetOriginTool set_origin;
+		EditorResizeTool resize;
 	};
 	explicit EditorInteractive(Widelands::EditorGameBase&);
 
@@ -162,6 +165,7 @@
 	UI::UniqueWindow::Registry immovablemenu_;
 	UI::UniqueWindow::Registry crittermenu_;
 	UI::UniqueWindow::Registry resourcesmenu_;
+	UI::UniqueWindow::Registry resizemenu_;
 	UI::UniqueWindow::Registry helpmenu_;
 
 	UI::Button* toggle_buildhelp_;

=== modified file 'src/editor/tools/action_args.h'
--- src/editor/tools/action_args.h	2019-02-23 11:00:49 +0000
+++ src/editor/tools/action_args.h	2019-04-07 17:11:51 +0000
@@ -21,9 +21,13 @@
 #define WL_EDITOR_TOOLS_ACTION_ARGS_H
 
 #include <list>
+#include <map>
+#include <set>
 #include <string>
+#include <vector>
 
 #include "logic/field.h"
+#include "logic/map.h"
 #include "logic/widelands_geometry.h"
 
 namespace Widelands {
@@ -52,12 +56,19 @@
 	std::list<Widelands::Field::Height> original_heights;  // change height tool
 	Widelands::DescriptionIndex current_resource;          // resources change tools
 	Widelands::ResourceAmount set_to;                      // resources change tools
+	Widelands::Extent new_map_size;                                   // resize tool
 
 	struct ResourceState {
 		Widelands::FCoords location;
 		Widelands::DescriptionIndex idx;
 		Widelands::ResourceAmount amount;
 	};
+	struct ResizeHistory {
+		Widelands::Extent old_map_size = Widelands::Extent(0, 0);
+		std::map<Widelands::Coords, Widelands::FieldData> deleted_fields;
+		std::set<Widelands::Coords> port_spaces;
+		std::vector<Widelands::Coords> starting_positions;
+	};
 
 	std::list<ResourceState> original_resource;                        // resources set tool
 	std::list<const Widelands::BobDescr*> old_bob_type, new_bob_type;  // bob change tools
@@ -65,6 +76,7 @@
 	std::list<Widelands::DescriptionIndex> new_immovable_types;        // immovable change tools
 	Widelands::HeightInterval interval;                                // noise height tool
 	std::list<Widelands::DescriptionIndex> terrain_type, original_terrain_type;  // set terrain tool
+	ResizeHistory resized;                                             // resize tool
 
 	std::list<EditorToolAction*> draw_actions;  // draw tool
 

=== modified file 'src/editor/tools/history.cc'
--- src/editor/tools/history.cc	2019-03-10 06:34:41 +0000
+++ src/editor/tools/history.cc	2019-04-07 17:11:51 +0000
@@ -35,6 +35,7 @@
      change_by(0),
      current_resource(0),
      set_to(0),
+     new_map_size(0, 0),
      interval(0, 0),
      refcount(0) {
 }

=== added file 'src/editor/tools/resize_tool.cc'
--- src/editor/tools/resize_tool.cc	1970-01-01 00:00:00 +0000
+++ src/editor/tools/resize_tool.cc	2019-04-07 17:11:51 +0000
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2002-2019 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include "editor/tools/resize_tool.h"
+
+#include "editor/editorinteractive.h"
+#include "logic/field.h"
+#include "logic/widelands_geometry.h"
+
+int32_t EditorResizeTool::handle_click_impl(const Widelands::World& world,
+                                                  const Widelands::NodeAndTriangle<>& center,
+                                                  EditorInteractive& parent,
+                                                  EditorActionArgs* args,
+                                                  Widelands::Map* map) {
+	Widelands::EditorGameBase& egbase = parent.egbase();
+
+	args->resized.old_map_size = map->extent();
+	args->resized.port_spaces.clear();
+	args->resized.starting_positions.clear();
+	for (const Widelands::Coords& ps : map->get_port_spaces()) {
+		args->resized.port_spaces.insert(Widelands::Coords(ps));
+	}
+	for (uint8_t i = 1; i <= map->get_nrplayers(); ++i) {
+		args->resized.starting_positions.push_back(map->get_starting_pos(i));
+	}
+
+	args->resized.deleted_fields = map->resize(egbase, center.node, args->new_map_size.w, args->new_map_size.h);
+
+	map->recalc_whole_map(world);
+	return 0;
+}
+
+int32_t EditorResizeTool::handle_undo_impl(
+   const Widelands::World& world,
+   const Widelands::NodeAndTriangle<Widelands::Coords>& center,
+   EditorInteractive& parent,
+   EditorActionArgs* args,
+   Widelands::Map* map) {
+	Widelands::EditorGameBase& egbase = parent.egbase();
+
+	map->resize(egbase, center.node, args->resized.old_map_size.w, args->resized.old_map_size.h);
+
+	for (const auto& it : args->resized.deleted_fields) {
+		const Widelands::FCoords f = map->get_fcoords(it.first);
+		const Widelands::FieldData data = it.second;
+
+		if (Widelands::BaseImmovable* imm = f.field->get_immovable()) {
+			imm->remove(egbase);
+		}
+		for (Widelands::Bob* bob = f.field->get_first_bob(); bob; bob = bob->get_next_bob()) {
+			bob->remove(egbase);
+		}
+
+		f.field->set_height(data.height);
+		f.field->set_terrains(data.terrains);
+		map->initialize_resources(f, data.resources, data.resource_amount);
+
+		if (data.immovable != "") {
+			egbase.create_immovable_with_name(f, data.immovable,
+					Widelands::MapObjectDescr::OwnerType::kWorld, nullptr, nullptr);
+		}
+		for (const std::string& bob : data.bobs) {
+			egbase.create_critter(f, bob);
+		}
+	}
+
+	for (const Widelands::Coords& c : args->resized.port_spaces) {
+		map->set_port_space(world, c, true, true);
+	}
+	for (uint8_t i = 1; i <= map->get_nrplayers(); ++i) {
+		map->set_starting_pos(i, args->resized.starting_positions[i - 1]);
+	}
+
+	map->recalc_whole_map(world);
+	return 0;
+}
+
+EditorActionArgs EditorResizeTool::format_args_impl(EditorInteractive& parent) {
+	EditorActionArgs a(parent);
+	a.new_map_size = Widelands::Extent(width_, height_);
+	return a;
+}
+

=== added file 'src/editor/tools/resize_tool.h'
--- src/editor/tools/resize_tool.h	1970-01-01 00:00:00 +0000
+++ src/editor/tools/resize_tool.h	2019-04-07 17:11:51 +0000
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2002-2019 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_EDITOR_TOOLS_RESIZE_TOOL_H
+#define WL_EDITOR_TOOLS_RESIZE_TOOL_H
+
+#include "editor/tools/tool.h"
+
+///  Resize the map
+struct EditorResizeTool : public EditorTool {
+	EditorResizeTool(int16_t width, int16_t height) : EditorTool(*this, *this), width_(width), height_(height) {
+	}
+
+	/**
+	 * Change the map size
+	 */
+	int32_t handle_click_impl(const Widelands::World& world,
+	                          const Widelands::NodeAndTriangle<>& center,
+	                          EditorInteractive& parent,
+	                          EditorActionArgs* args,
+	                          Widelands::Map* map) override;
+
+	int32_t handle_undo_impl(const Widelands::World& world,
+	                         const Widelands::NodeAndTriangle<>& center,
+	                         EditorInteractive& parent,
+	                         EditorActionArgs* args,
+	                         Widelands::Map* map) override;
+
+	EditorActionArgs format_args_impl(EditorInteractive& parent) override;
+
+	const Image* get_sel_impl() const override {
+		return g_gr->images().get("images/wui/editor/fsel_editor_resize.png");
+	}
+
+	bool has_size_one() const override {
+		return true;
+	}
+
+	void set_width(uint32_t w) {
+		width_ = w;
+	}
+
+	uint32_t get_width() {
+		return width_;
+	}
+
+	void set_height(uint32_t h) {
+		height_ = h;
+	}
+
+	uint32_t get_height() {
+		return height_;
+	}
+
+private:
+	uint32_t width_;
+	uint32_t height_;
+};
+
+#endif  // end of include guard: WL_EDITOR_TOOLS_RESIZE_TOOL_H

=== modified file 'src/editor/ui_menus/tool_menu.cc'
--- src/editor/ui_menus/tool_menu.cc	2019-02-23 11:00:49 +0000
+++ src/editor/ui_menus/tool_menu.cc	2019-04-07 17:11:51 +0000
@@ -36,6 +36,7 @@
 #include "editor/ui_menus/tool_noise_height_options_menu.h"
 #include "editor/ui_menus/tool_place_critter_options_menu.h"
 #include "editor/ui_menus/tool_place_immovable_options_menu.h"
+#include "editor/ui_menus/tool_resize_options_menu.h"
 #include "editor/ui_menus/tool_set_terrain_options_menu.h"
 #include "graphic/graphic.h"
 #include "ui_basic/radiobutton.h"
@@ -50,7 +51,7 @@
 	int32_t const width = 34;
 	int32_t const height = 34;
 
-	int32_t const num_tools = 8;
+	int32_t const num_tools = 9;
 #define ADD_BUTTON(pic, tooltip)                                                                   \
 	radioselect_.add_button(                                                                        \
 	   this, pos, g_gr->images().get("images/wui/editor/editor_menu_tool_" pic ".png"), tooltip);   \
@@ -65,6 +66,7 @@
 	ADD_BUTTON("set_port_space", _("Set port space"));
 	ADD_BUTTON("set_origin", _("Set the position that will have the coordinates (0, 0). This will "
 	                           "be the top-left corner of a generated minimap."));
+	ADD_BUTTON("resize", _("Insert or delete rows and columns"));
 
 	set_inner_size(offs.x + (width + spacing) * num_tools, offs.y + (height + spacing));
 
@@ -82,7 +84,10 @@
 		                          5 :
 		                          &current == &parent.tools()->set_port_space ?
 		                          6 :
-		                          &current == &parent.tools()->set_origin ? 7 : 0);
+		                          &current == &parent.tools()->set_origin ?
+		                          7 :
+		                          &current == &parent.tools()->resize ?
+		                          8 : 0);
 	}
 
 	radioselect_.changed.connect(boost::bind(&EditorToolMenu::changed_to, this));
@@ -135,6 +140,10 @@
 		current_tool_pointer = &parent.tools()->set_origin;
 		current_registry_pointer = nullptr;  // no need for a window
 		break;
+	case 8:
+		current_tool_pointer = &parent.tools()->resize;
+		current_registry_pointer = &parent.resizemenu_;
+		break;
 	default:
 		NEVER_HERE();
 	}
@@ -174,6 +183,10 @@
 				new EditorToolChangeResourcesOptionsMenu(
 				   parent, parent.tools()->increase_resources, *current_registry_pointer);
 				break;
+			case 8:
+				new EditorToolResizeOptionsMenu(
+				   parent, parent.tools()->resize, *current_registry_pointer);
+				break;
 			default:
 				NEVER_HERE();
 			}

=== added file 'src/editor/ui_menus/tool_resize_options_menu.cc'
--- src/editor/ui_menus/tool_resize_options_menu.cc	1970-01-01 00:00:00 +0000
+++ src/editor/ui_menus/tool_resize_options_menu.cc	2019-04-07 17:11:51 +0000
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2002-2019 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include "editor/ui_menus/tool_resize_options_menu.h"
+
+#include <cstdio>
+#include <string>
+
+#include "base/i18n.h"
+#include "base/wexception.h"
+#include "editor/editorinteractive.h"
+#include "editor/tools/resize_tool.h"
+#include "graphic/graphic.h"
+#include "logic/map.h"
+
+inline EditorInteractive& EditorToolResizeOptionsMenu::eia() {
+	return dynamic_cast<EditorInteractive&>(*get_parent());
+}
+
+EditorToolResizeOptionsMenu::EditorToolResizeOptionsMenu(
+   EditorInteractive& parent,
+   EditorResizeTool& resize_tool,
+   UI::UniqueWindow::Registry& registry)
+   : EditorToolOptionsMenu(parent, registry, 260, 200, _("Resize")),
+     resize_tool_(resize_tool),
+     box_(this, hmargin(), vmargin(), UI::Box::Vertical, 0, 0, vspacing()),
+     new_width_(&box_,
+                0,
+                0,
+                get_inner_w() - 2 * hmargin(),
+                80,
+                0,
+                1,
+                1000,
+                UI::PanelStyle::kWui,
+                _("New width:"),
+                UI::SpinBox::Units::kNone,
+                UI::SpinBox::Type::kValueList),
+     new_height_(&box_,
+             0,
+             0,
+             get_inner_w() - 2 * hmargin(),
+             80,
+             0,
+             1,
+             1000,
+             UI::PanelStyle::kWui,
+             _("New height:"),
+             UI::SpinBox::Units::kNone,
+             UI::SpinBox::Type::kValueList) {
+	// Configure spin boxes
+	new_width_.set_value_list(Widelands::kMapDimensions);
+	new_height_.set_value_list(Widelands::kMapDimensions);
+	{
+		size_t width_index, height_index;
+		Widelands::Extent const map_extent = parent.egbase().map().extent();
+		for (width_index = 0; width_index < Widelands::kMapDimensions.size() &&
+		                      Widelands::kMapDimensions[width_index] < map_extent.w;
+		     ++width_index) {
+		}
+		new_width_.set_value(width_index);
+
+		for (height_index = 0; height_index < Widelands::kMapDimensions.size() &&
+		                       Widelands::kMapDimensions[height_index] < map_extent.h;
+		     ++height_index) {
+		}
+		new_height_.set_value(height_index);
+	}
+
+	new_width_.changed.connect(
+	   boost::bind(&EditorToolResizeOptionsMenu::update_width, boost::ref(*this)));
+	new_height_.changed.connect(
+	   boost::bind(&EditorToolResizeOptionsMenu::update_height, boost::ref(*this)));
+
+	box_.add(&new_width_);
+	box_.add(&new_height_);
+
+	box_.set_size(get_inner_w() - 2 * hmargin(), new_width_.get_h() + new_height_.get_h() + vspacing());
+	set_inner_size(get_inner_w(), box_.get_h() + 1 * vmargin());
+}
+
+void EditorToolResizeOptionsMenu::update_width() {
+	int32_t w = new_width_.get_value();
+	assert(w > 0);
+	resize_tool_.set_width(w);
+	select_correct_tool();
+}
+
+void EditorToolResizeOptionsMenu::update_height() {
+	int32_t h = new_height_.get_value();
+	assert(h > 0);
+	resize_tool_.set_height(h);
+	select_correct_tool();
+}
+

=== added file 'src/editor/ui_menus/tool_resize_options_menu.h'
--- src/editor/ui_menus/tool_resize_options_menu.h	1970-01-01 00:00:00 +0000
+++ src/editor/ui_menus/tool_resize_options_menu.h	2019-04-07 17:11:51 +0000
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2002-2019 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_EDITOR_UI_MENUS_TOOL_RESIZE_OPTIONS_MENU_H
+#define WL_EDITOR_UI_MENUS_TOOL_RESIZE_OPTIONS_MENU_H
+
+#include "editor/ui_menus/tool_options_menu.h"
+#include "ui_basic/box.h"
+#include "ui_basic/spinbox.h"
+#include "ui_basic/textarea.h"
+
+class EditorInteractive;
+struct EditorResizeTool;
+
+struct EditorToolResizeOptionsMenu : public EditorToolOptionsMenu {
+	EditorToolResizeOptionsMenu(EditorInteractive&,
+	                                     EditorResizeTool&,
+	                                     UI::UniqueWindow::Registry&);
+
+private:
+	EditorInteractive& eia();
+	void update_width();
+	void update_height();
+
+	EditorResizeTool& resize_tool_;
+	UI::Box box_;
+	UI::SpinBox new_width_;
+	UI::SpinBox new_height_;
+};
+
+#endif  // end of include guard: WL_EDITOR_UI_MENUS_TOOL_RESIZE_OPTIONS_MENU_H

=== modified file 'src/logic/map.cc'
--- src/logic/map.cc	2019-03-24 13:16:11 +0000
+++ src/logic/map.cc	2019-04-07 17:11:51 +0000
@@ -54,6 +54,21 @@
 
 namespace Widelands {
 
+FieldData::FieldData(const Field& field)
+		: height(field.get_height()),
+		resources(field.get_resources()),
+		resource_amount(field.get_initial_res_amount()),
+		terrains(field.get_terrains()) {
+	if (const BaseImmovable* imm = field.get_immovable()) {
+		immovable = imm->descr().name();
+	} else {
+		immovable = "";
+	}
+	for (Bob* bob = field.get_first_bob(); bob; bob = bob->get_next_bob()) {
+		bobs.push_back(bob->descr().name());
+	}
+}
+
 /*
 ==============================================================================
 
@@ -456,6 +471,11 @@
 	filesystem_.reset(nullptr);
 }
 
+// Made this a separate function to reduce compiler warnings
+static inline void clear_array(std::unique_ptr<Field[]>* fields, uint32_t size) {
+	memset(fields->get(), 0, sizeof(Field) * size);
+}
+
 void Map::set_origin(const Coords& new_origin) {
 	assert(0 <= new_origin.x);
 	assert(new_origin.x < width_);
@@ -469,7 +489,7 @@
 	}
 
 	std::unique_ptr<Field[]> new_field_order(new Field[field_size]);
-	memset(new_field_order.get(), 0, sizeof(Field) * field_size);
+	clear_array(&new_field_order, field_size);
 
 	// Rearrange The fields
 	// NOTE because of the triangle design, we have to take special care of cases
@@ -524,6 +544,153 @@
 	log("Map origin was shifted by (%d, %d)\n", new_origin.x, new_origin.y);
 }
 
+/* Helper function for resize():
+ * Calculates the coords of 'c' after resizing the map from the given old size to the given new size at 'split'.
+ */
+static Coords transform_coords(const Coords& c, const Coords& split,
+		int16_t w_new, int16_t h_new, int16_t w_old, int16_t h_old) {
+	const int16_t delta_w = w_new - w_old;
+	const int16_t delta_h = h_new - h_old;
+	if (c.x < split.x && c.y < split.y) {
+		// Nothing to shift
+		Coords result(c);
+		Map::normalize_coords(result, w_new, h_new);
+		return result;
+	} else if ((w_new < w_old && c.x >= split.x && c.x < split.x - delta_w) ||
+			(h_new < h_old && c.y >= split.y && c.y < split.y - delta_h)) {
+		// Field removed
+		return Coords::null();
+	}
+	Coords result(c.x, c.y);
+	if (c.x >= split.x) {
+		result.x += delta_w;
+	}
+	if (c.y >= split.y) {
+		result.y += delta_h;
+	}
+	Map::normalize_coords(result, w_new, h_new);
+	return result;
+}
+
+/* Change the size of the (already initialized) map to 'w'×'h' by inserting/deleting fields south and east of 'split'.
+ * Returns the data of fields that were deleted during resizing.
+ * This function will notify all players of the change in map size, but not of anything else. This is because
+ * the editor may want to do some post-resize cleanup first, and this function is intended to be used only
+ * by the editor anyway.
+ * You should call recalc_whole_map() afterwards to resolve height differences etc.
+ */
+std::map<Coords, FieldData> Map::resize(EditorGameBase& egbase, const Coords split, const int32_t w, const int32_t h) {
+	assert(w > 0);
+	assert(h > 0);
+
+	std::map<Coords, FieldData> deleted;
+	if (w == width_ && h == height_) {
+		return deleted;
+	}
+
+	const uint32_t field_size = w * h;
+	const uint32_t old_field_size = width_ * height_;
+
+	std::unique_ptr<Field[]> new_fields(new Field[field_size]);
+	clear_array(&new_fields, field_size);
+
+	// Take care of starting positions and port spaces
+	for (uint8_t i = get_nrplayers(); i;) {
+		--i;
+		if (starting_pos_[i]) {
+			starting_pos_[i] = transform_coords(starting_pos_[i], split, w, h, width_, height_);
+		}
+	}
+
+	PortSpacesSet new_port_spaces;
+	for (Coords it : port_spaces_) {
+		if (Coords c = transform_coords(it, split, w, h, width_, height_)) {
+			new_port_spaces.insert(c);
+		}
+	}
+	port_spaces_ = new_port_spaces;
+
+	Field::Terrains default_terrains;
+	default_terrains.r = 0;
+	default_terrains.d = 0;
+
+	std::unique_ptr<bool[]> preserved_coords(new bool[old_field_size]);
+	memset(preserved_coords.get(), false, sizeof(bool) * old_field_size);
+
+	for (int16_t x = 0; x < w; ++x) {
+		for (int16_t y = 0; y < h; ++y) {
+			Coords c_new = Coords(x, y);
+			if (Coords c_old = transform_coords(c_new, split, width_, height_, w, h)) {
+				bool& entry = preserved_coords[get_index(c_old, width_)];
+				if (!entry) {
+					// Copy existing field
+					entry = true;
+					new_fields[get_index(c_new, w)] = operator[](c_old);
+					continue;
+				}
+			}
+			// Init new field
+			Field& field = new_fields[get_index(c_new, w)];
+			field.set_height(10);
+			field.set_terrains(default_terrains);
+		}
+	}
+
+	for (int16_t x = 0; x < width_; ++x) {
+		for (int16_t y = 0; y < height_; ++y) {
+			Coords c(x, y);
+			if (!preserved_coords[get_index(c, width_)]) {
+				// Save the data of fields that will be deleted
+				Field& field = operator[](c);
+				deleted.emplace(c, FieldData(field));
+				// ...and now we delete stuff that needs removing when the field is destroyed
+				if (BaseImmovable* imm = field.get_immovable()) {
+					imm->remove(egbase);
+				}
+				while (Bob* bob = field.get_first_bob()) {
+					bob->remove(egbase);
+				}
+			}
+		}
+	}
+
+	// Replace all fields
+	fields_.reset(new Field[field_size]);
+	clear_array(&fields_, field_size);
+	for (size_t ind = 0; ind < field_size; ind++) {
+		fields_[ind] = new_fields[ind];
+	}
+	log("Resized map from (%d, %d) to (%u, %u) at (%d, %d)\n", width_, height_, w, h, split.x, split.y);
+	width_ = w;
+	height_ = h;
+
+	// Inform immovables and bobs about their new position
+	for (int16_t x = 0; x < w; ++x) {
+		for (int16_t y = 0; y < h; ++y) {
+			FCoords f = get_fcoords(Coords(x, y));
+			if (upcast(Immovable, immovable, f.field->get_immovable())) {
+				immovable->position_ = f;
+			}
+			// Ensuring that all bob iterators are changed correctly is a bit hacky, but the more obvious
+			// solution of doing it like in set_origin() is highly problematic here, or so ASan tells me
+			std::vector<Bob*> bobs;
+			for (Bob* bob = f.field->get_first_bob(); bob; bob = bob->get_next_bob()) {
+				bobs.push_back(bob);
+			}
+			f.field->bobs = nullptr;
+			for (Bob* bob : bobs) {
+				bob->position_.field = nullptr;
+				bob->linknext_ = nullptr;
+				bob->linkpprev_ = nullptr;
+				bob->set_position(egbase, f);
+			}
+		}
+	}
+
+	egbase.allocate_player_maps();
+	return deleted;
+}
+
 /*
 ===============
 Set the size of the map. This should only happen once during initial load.
@@ -535,10 +702,12 @@
 	width_ = w;
 	height_ = h;
 
-	fields_.reset(new Field[w * h]);
-	memset(fields_.get(), 0, sizeof(Field) * w * h);
-
-	pathfieldmgr_->set_size(w * h);
+	const uint32_t field_size = w * h;
+
+	fields_.reset(new Field[field_size]);
+	clear_array(&fields_, field_size);
+
+	pathfieldmgr_->set_size(field_size);
 }
 
 /*

=== modified file 'src/logic/map.h'
--- src/logic/map.h	2019-03-24 13:16:11 +0000
+++ src/logic/map.h	2019-04-07 17:11:51 +0000
@@ -104,6 +104,18 @@
 	}  // make gcc shut up
 };
 
+// Helper struct to save certain elemental data of a field without an actual instance of Field
+struct FieldData {
+	FieldData(const Field& f);
+
+	std::string immovable;
+	std::list<std::string> bobs;
+	uint8_t height;
+	DescriptionIndex resources;
+	uint8_t resource_amount;
+	Field::Terrains terrains;
+};
+
 /** class Map
  *
  * This really identifies a map like it is in the game
@@ -327,6 +339,7 @@
 	Field& operator[](MapIndex) const;
 	Field& operator[](const Coords&) const;
 	FCoords get_fcoords(const Coords&) const;
+	static void normalize_coords(Coords&, int16_t, int16_t);
 	void normalize_coords(Coords&) const;
 	FCoords get_fcoords(Field&) const;
 	void get_coords(Field& f, Coords& c) const;
@@ -498,6 +511,9 @@
 	// Visible for testing.
 	void set_size(uint32_t w, uint32_t h);
 
+	// Change the map size
+	std::map<Coords, FieldData> resize(EditorGameBase& egbase, const Coords coords, int32_t w, int32_t h);
+
 private:
 	void recalc_border(const FCoords&);
 	void recalc_brightness(const FCoords&);
@@ -588,14 +604,18 @@
 }
 
 inline void Map::normalize_coords(Coords& c) const {
+	normalize_coords(c, width_, height_);
+}
+
+inline void Map::normalize_coords(Coords& c, int16_t w, int16_t h) {
 	while (c.x < 0)
-		c.x += width_;
-	while (c.x >= width_)
-		c.x -= width_;
+		c.x += w;
+	while (c.x >= w)
+		c.x -= w;
 	while (c.y < 0)
-		c.y += height_;
-	while (c.y >= height_)
-		c.y -= height_;
+		c.y += h;
+	while (c.y >= h)
+		c.y -= h;
 }
 
 /**


Follow ups