Bläddra i källkod

Merge pull request #24645 from YeldhamDev/tileset_editor_undo

Add undo-redo to the TileSet editor, and other improvements
Rémi Verschelde 6 år sedan
förälder
incheckning
c64b07b264

+ 1 - 1
doc/classes/FileDialog.xml

@@ -76,7 +76,7 @@
 			Set dialog to open or save mode, changes selection behavior. See enum [code]Mode[/code] constants.
 		</member>
 		<member name="mode_overrides_title" type="bool" setter="set_mode_overrides_title" getter="is_mode_overriding_title">
-			If [code]true[/code], changing the [code]Mode[/code] property will set the window title accordingly (e. g. setting mode to [code]MODE_OPEN_FILE[/code] will change the window title to "Open a File").
+			If [code]true[/code], changing the [code]Mode[/code] property will set the window title accordingly (e.g. setting mode to [code]MODE_OPEN_FILE[/code] will change the window title to "Open a File").
 		</member>
 		<member name="show_hidden_files" type="bool" setter="set_show_hidden_files" getter="is_showing_hidden_files">
 			If [code]true[/code], the dialog will show hidden files.

+ 181 - 2
doc/classes/TileSet.xml

@@ -36,12 +36,66 @@
 			<description>
 			</description>
 		</method>
+		<method name="autotile_clear_bitmask_map">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<description>
+				Clears all bitmask info of the autotile.
+			</description>
+		</method>
+		<method name="autotile_get_bitmask">
+			<return type="int" enum="TileSet.AutotileBindings">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="coord" type="Vector2">
+			</argument>
+			<description>
+				Returns the bitmask of the subtile from an autotile given its coordinates.
+				The value is the sum of the values in [enum TileSet.AutotileBindings] present in the subtile (e.g. a value of 5 means the bitmask has bindings in both the top left and top right).
+			</description>
+		</method>
 		<method name="autotile_get_bitmask_mode" qualifiers="const">
 			<return type="int" enum="TileSet.BitmaskMode">
 			</return>
 			<argument index="0" name="id" type="int">
 			</argument>
 			<description>
+				Returns the [enum TileSet.BitmaskMode] of the autotile.
+			</description>
+		</method>
+		<method name="autotile_get_icon_coordinate" qualifiers="const">
+			<return type="Vector2">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<description>
+				Returns the subtile that's being used as an icon in an atlas/autotile given its coordinates.
+				The subtile defined as the icon will be used as a fallback when the atlas/autotile's bitmask info is incomplete. It will also be used to represent it in the TileSet editor.
+			</description>
+		</method>
+		<method name="autotile_get_light_occluder" qualifiers="const">
+			<return type="OccluderPolygon2D">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="coord" type="Vector2">
+			</argument>
+			<description>
+				Returns the light occluder of the subtile from an atlas/autotile given its coordinates.
+			</description>
+		</method>
+		<method name="autotile_get_navigation_polygon" qualifiers="const">
+			<return type="NavigationPolygon">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="coord" type="Vector2">
+			</argument>
+			<description>
+				Returns the navigation polygon of the subtile from an atlas/autotile given its coordinates.
 			</description>
 		</method>
 		<method name="autotile_get_size" qualifiers="const">
@@ -50,6 +104,53 @@
 			<argument index="0" name="id" type="int">
 			</argument>
 			<description>
+				Returns the size of the subtiles in an atlas/autotile.
+			</description>
+		</method>
+		<method name="autotile_get_spacing" qualifiers="const">
+			<return type="int">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<description>
+				Returns the spacing between subtiles of the atlas/autotile.
+			</description>
+		</method>
+		<method name="autotile_get_subtile_priority">
+			<return type="int">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="coord" type="Vector2">
+			</argument>
+			<description>
+				Returns the priority of the subtile from an autotile given its coordinates.
+				When more than one subtile has the same bitmask value, one of them will be picked randomly for drawing. Its priority will define how often it will be picked.
+			</description>
+		</method>
+		<method name="autotile_get_z_index">
+			<return type="int">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="coord" type="Vector2">
+			</argument>
+			<description>
+				Returns the drawing index of the subtile from an atlas/autotile given its coordinates.
+			</description>
+		</method>
+		<method name="autotile_set_bitmask">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="bitmask" type="Vector2">
+			</argument>
+			<argument index="2" name="flag" type="int" enum="TileSet.AutotileBindings">
+			</argument>
+			<description>
+				Sets the bitmask of the subtile from an autotile given its coordinates.
+				The value is the sum of the values in [enum TileSet.AutotileBindings] present in the subtile (e.g. a value of 5 means the bitmask has bindings in both the top left and top right).
 			</description>
 		</method>
 		<method name="autotile_set_bitmask_mode">
@@ -60,6 +161,45 @@
 			<argument index="1" name="mode" type="int" enum="TileSet.BitmaskMode">
 			</argument>
 			<description>
+				Sets the [enum TileSet.BitmaskMode] of the autotile.
+			</description>
+		</method>
+		<method name="autotile_set_icon_coordinate">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="coord" type="Vector2">
+			</argument>
+			<description>
+				Sets the subtile that will be used as an icon in an atlas/autotile given its coordinates.
+				The subtile defined as the icon will be used as a fallback when the atlas/autotile's bitmask info is incomplete. It will also be used to represent it in the TileSet editor.
+			</description>
+		</method>
+		<method name="autotile_set_light_occluder">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="light_occluder" type="OccluderPolygon2D">
+			</argument>
+			<argument index="2" name="coord" type="Vector2">
+			</argument>
+			<description>
+				Sets the light occluder of the subtile from an atlas/autotile given its coordinates.
+			</description>
+		</method>
+		<method name="autotile_set_navigation_polygon">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="navigation_polygon" type="NavigationPolygon">
+			</argument>
+			<argument index="2" name="coord" type="Vector2">
+			</argument>
+			<description>
+				Sets the navigation polygon of the subtile from an atlas/autotile given its coordinates.
 			</description>
 		</method>
 		<method name="autotile_set_size">
@@ -70,6 +210,45 @@
 			<argument index="1" name="size" type="Vector2">
 			</argument>
 			<description>
+				Sets the size of the subtiles in an atlas/autotile.
+			</description>
+		</method>
+		<method name="autotile_set_spacing">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="spacing" type="int">
+			</argument>
+			<description>
+				Sets the spacing between subtiles of the atlas/autotile.
+			</description>
+		</method>
+		<method name="autotile_set_subtile_priority">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="coord" type="Vector2">
+			</argument>
+			<argument index="2" name="priority" type="int">
+			</argument>
+			<description>
+				Sets the priority of the subtile from an autotile given its coordinates.
+				When more than one subtile has the same bitmask value, one of them will be picked randomly for drawing. Its priority will define how often it will be picked.
+			</description>
+		</method>
+		<method name="autotile_set_z_index">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="coord" type="Vector2">
+			</argument>
+			<argument index="2" name="z_index" type="int">
+			</argument>
+			<description>
+				Sets the drawing index of the subtile from an atlas/autotile given its coordinates.
 			</description>
 		</method>
 		<method name="clear">
@@ -304,7 +483,7 @@
 			<argument index="0" name="id" type="int">
 			</argument>
 			<description>
-				Returns the tile's [enum TileMode].
+				Returns the tile's [enum TileSet.TileMode].
 			</description>
 		</method>
 		<method name="tile_get_z_index" qualifiers="const">
@@ -508,7 +687,7 @@
 			<argument index="1" name="tilemode" type="int" enum="TileSet.TileMode">
 			</argument>
 			<description>
-				Sets the tile's [enum TileMode].
+				Sets the tile's [enum TileSet.TileMode].
 			</description>
 		</method>
 		<method name="tile_set_z_index">

+ 1 - 1
editor/plugins/tile_map_editor_plugin.cpp

@@ -517,7 +517,7 @@ void TileMapEditor::_update_palette() {
 		manual_palette->show();
 	}
 
-	if (tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) {
+	if (sel_tile != TileMap::INVALID_CELL && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) {
 		manual_button->show();
 	} else {
 		manual_button->hide();

+ 384 - 198
editor/plugins/tile_set_editor_plugin.cpp

@@ -165,6 +165,11 @@ void TileSetEditor::_import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_
 	_import_node(p_scene, p_library);
 }
 
+void TileSetEditor::_undo_redo_import_scene(Node *p_scene, bool p_merge) {
+
+	_import_scene(p_scene, tileset, p_merge);
+}
+
 Error TileSetEditor::update_library_file(Node *p_base_scene, Ref<TileSet> ml, bool p_merge) {
 
 	_import_scene(p_base_scene, ml, p_merge);
@@ -173,6 +178,7 @@ Error TileSetEditor::update_library_file(Node *p_base_scene, Ref<TileSet> ml, bo
 
 void TileSetEditor::_bind_methods() {
 
+	ClassDB::bind_method("_undo_redo_import_scene", &TileSetEditor::_undo_redo_import_scene);
 	ClassDB::bind_method("_on_tileset_toolbar_button_pressed", &TileSetEditor::_on_tileset_toolbar_button_pressed);
 	ClassDB::bind_method("_on_textures_added", &TileSetEditor::_on_textures_added);
 	ClassDB::bind_method("_on_tileset_toolbar_confirm", &TileSetEditor::_on_tileset_toolbar_confirm);
@@ -190,9 +196,16 @@ void TileSetEditor::_bind_methods() {
 	ClassDB::bind_method("_set_snap_step", &TileSetEditor::_set_snap_step);
 	ClassDB::bind_method("_set_snap_off", &TileSetEditor::_set_snap_off);
 	ClassDB::bind_method("_set_snap_sep", &TileSetEditor::_set_snap_sep);
+	ClassDB::bind_method("_validate_current_tile_id", &TileSetEditor::_validate_current_tile_id);
 	ClassDB::bind_method("_zoom_in", &TileSetEditor::_zoom_in);
 	ClassDB::bind_method("_zoom_out", &TileSetEditor::_zoom_out);
 	ClassDB::bind_method("_zoom_reset", &TileSetEditor::_zoom_reset);
+	ClassDB::bind_method("_select_edited_shape_coord", &TileSetEditor::_select_edited_shape_coord);
+
+	ClassDB::bind_method("edit", &TileSetEditor::edit);
+	ClassDB::bind_method("add_texture", &TileSetEditor::add_texture);
+	ClassDB::bind_method("remove_texture", &TileSetEditor::remove_texture);
+	ClassDB::bind_method("update_texture_list_icon", &TileSetEditor::update_texture_list_icon);
 }
 
 void TileSetEditor::_notification(int p_what) {
@@ -244,6 +257,7 @@ void TileSetEditor::_notification(int p_what) {
 TileSetEditor::TileSetEditor(EditorNode *p_editor) {
 
 	editor = p_editor;
+	undo_redo = editor->get_undo_redo();
 	current_tile = -1;
 
 	VBoxContainer *left_container = memnew(VBoxContainer);
@@ -502,7 +516,7 @@ void TileSetEditor::_on_tileset_toolbar_button_pressed(int p_index) {
 		} break;
 		case TOOL_TILESET_REMOVE_TEXTURE: {
 			if (get_current_texture().is_valid()) {
-				cd->set_text(TTR("Remove selected texture and ALL TILES which use it?"));
+				cd->set_text(TTR("Remove selected texture? This will remove all tiles which use it."));
 				cd->popup_centered(Size2(300, 60));
 			} else {
 				err_dialog->set_text(TTR("You haven't selected a texture to remove."));
@@ -511,7 +525,7 @@ void TileSetEditor::_on_tileset_toolbar_button_pressed(int p_index) {
 		} break;
 		case TOOL_TILESET_CREATE_SCENE: {
 
-			cd->set_text(TTR("Create from scene?"));
+			cd->set_text(TTR("Create from scene? This will overwrite all current tiles."));
 			cd->popup_centered(Size2(300, 60));
 		} break;
 		case TOOL_TILESET_MERGE_SCENE: {
@@ -528,14 +542,18 @@ void TileSetEditor::_on_tileset_toolbar_confirm() {
 			RID current_rid = get_current_texture()->get_rid();
 			List<int> ids;
 			tileset->get_tile_list(&ids);
+
+			undo_redo->create_action(TTR("Remove Texture"));
 			for (List<int>::Element *E = ids.front(); E; E = E->next()) {
 				if (tileset->tile_get_texture(E->get())->get_rid() == current_rid) {
-					tileset->remove_tile(E->get());
+					undo_redo->add_do_method(tileset.ptr(), "remove_tile", E->get());
+					_undo_tile_removal(E->get());
 				}
 			}
-			texture_list->remove_item(texture_list->find_metadata(current_rid));
-			texture_map.erase(current_rid);
-			_on_texture_list_selected(-1);
+			undo_redo->add_do_method(this, "remove_texture", get_current_texture());
+			undo_redo->add_undo_method(this, "add_texture", get_current_texture());
+			undo_redo->add_undo_method(this, "update_texture_list_icon");
+			undo_redo->commit_action();
 		} break;
 		case TOOL_TILESET_MERGE_SCENE:
 		case TOOL_TILESET_CREATE_SCENE: {
@@ -544,9 +562,19 @@ void TileSetEditor::_on_tileset_toolbar_confirm() {
 			Node *scene = en->get_edited_scene();
 			if (!scene)
 				break;
-			_import_scene(scene, tileset, option == TOOL_TILESET_MERGE_SCENE);
 
-			edit(tileset);
+			List<int> ids;
+			tileset->get_tile_list(&ids);
+
+			undo_redo->create_action(TTR(option == TOOL_TILESET_MERGE_SCENE ? "Merge Tileset from Scene" : "Create Tileset from Scene"));
+			undo_redo->add_do_method(this, "_undo_redo_import_scene", scene, option == TOOL_TILESET_MERGE_SCENE);
+			undo_redo->add_undo_method(tileset.ptr(), "clear");
+			for (List<int>::Element *E = ids.front(); E; E = E->next()) {
+				_undo_tile_removal(E->get());
+			}
+			undo_redo->add_do_method(this, "edit", tileset);
+			undo_redo->add_undo_method(this, "edit", tileset);
+			undo_redo->commit_action();
 		} break;
 	}
 }
@@ -580,9 +608,7 @@ void TileSetEditor::_on_textures_added(const PoolStringArray &p_paths) {
 		if (texture_map.has(t->get_rid())) {
 			invalid_count++;
 		} else {
-			texture_list->add_item(t->get_path().get_file());
-			texture_map.insert(t->get_rid(), t);
-			texture_list->set_item_metadata(texture_list->get_item_count() - 1, t->get_rid());
+			add_texture(t);
 		}
 	}
 
@@ -653,7 +679,7 @@ void TileSetEditor::_on_edit_mode_changed(int p_edit_mode) {
 			spin_priority->hide();
 			spin_z_index->hide();
 
-			select_coord(edited_shape_coord);
+			_select_edited_shape_coord();
 		} break;
 		case EDITMODE_BITMASK: {
 			tools[TOOL_SELECT]->show();
@@ -964,7 +990,6 @@ void TileSetEditor::_on_workspace_overlay_draw() {
 	}
 }
 
-#define MIN_DISTANCE_SQUARED 6
 void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 
 	if (tileset.is_null() || !get_current_texture().is_valid())
@@ -1043,29 +1068,49 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 				edited_region.position -= WORKSPACE_MARGIN;
 				if (!edited_region.has_no_area()) {
 					if (get_current_tile() >= 0 && workspace_mode == WORKSPACE_EDIT) {
-						tileset->tile_set_region(get_current_tile(), edited_region);
+						undo_redo->create_action(TTR("Set Tile Region"));
+						undo_redo->add_do_method(tileset.ptr(), "tile_set_region", get_current_tile(), edited_region);
+						undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", get_current_tile(), tileset->tile_get_region(get_current_tile()));
+						edited_region = Rect2();
+						undo_redo->add_do_method(workspace, "update");
+						undo_redo->add_undo_method(workspace, "update");
+						undo_redo->add_do_method(workspace_overlay, "update");
+						undo_redo->add_undo_method(workspace_overlay, "update");
+						undo_redo->commit_action();
 					} else {
 						int t_id = tileset->get_last_unused_tile_id();
-						tileset->create_tile(t_id);
-						tileset->tile_set_texture(t_id, get_current_texture());
-						tileset->tile_set_region(t_id, edited_region);
-						tileset->tile_set_name(t_id, get_current_texture()->get_path().get_file() + " " + String::num(t_id, 0));
+						undo_redo->create_action(TTR("Create Tile"));
+						undo_redo->add_do_method(tileset.ptr(), "create_tile", t_id);
+						undo_redo->add_undo_method(tileset.ptr(), "remove_tile", t_id);
+						undo_redo->add_undo_method(this, "_validate_current_tile_id");
+						undo_redo->add_do_method(tileset.ptr(), "tile_set_texture", t_id, get_current_texture());
+						undo_redo->add_do_method(tileset.ptr(), "tile_set_region", t_id, edited_region);
+						undo_redo->add_do_method(tileset.ptr(), "tile_set_name", t_id, get_current_texture()->get_path().get_file() + " " + String::num(t_id, 0));
 						if (workspace_mode != WORKSPACE_CREATE_SINGLE) {
-							tileset->autotile_set_size(t_id, snap_step);
-							tileset->autotile_set_spacing(t_id, snap_separation.x);
-							tileset->tile_set_tile_mode(t_id, workspace_mode == WORKSPACE_CREATE_AUTOTILE ? TileSet::AUTO_TILE : TileSet::ATLAS_TILE);
+							undo_redo->add_do_method(tileset.ptr(), "autotile_set_size", t_id, snap_step);
+							undo_redo->add_do_method(tileset.ptr(), "autotile_set_spacing", t_id, snap_separation.x);
+							undo_redo->add_do_method(tileset.ptr(), "tile_set_tile_mode", t_id, workspace_mode == WORKSPACE_CREATE_AUTOTILE ? TileSet::AUTO_TILE : TileSet::ATLAS_TILE);
 						}
-						set_current_tile(t_id);
 
 						tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
 						tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
 						edit_mode = EDITMODE_COLLISION;
+						edited_region = Rect2();
+
+						undo_redo->add_do_method(workspace, "update");
+						undo_redo->add_undo_method(workspace, "update");
+						undo_redo->add_do_method(workspace_overlay, "update");
+						undo_redo->add_undo_method(workspace_overlay, "update");
+						undo_redo->commit_action();
+
+						set_current_tile(t_id);
 						_on_workspace_mode_changed(WORKSPACE_EDIT);
 					}
+				} else {
+					edited_region = Rect2();
+					workspace->update();
+					workspace_overlay->update();
 				}
-				edited_region = Rect2();
-				workspace->update();
-				workspace_overlay->update();
 				return;
 			}
 		} else if (mm.is_valid()) {
@@ -1078,8 +1123,8 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 			}
 		}
 	}
-	if (workspace_mode == WORKSPACE_EDIT) {
 
+	if (workspace_mode == WORKSPACE_EDIT) {
 		if (get_current_tile() >= 0) {
 			int spacing = tileset->autotile_get_spacing(get_current_tile());
 			Vector2 size = tileset->autotile_get_size(get_current_tile());
@@ -1088,13 +1133,12 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 					if (mb.is_valid()) {
 						if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && current_tile_region.has_point(mb->get_position())) {
 							Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
-							tileset->autotile_set_icon_coordinate(get_current_tile(), coord);
-							Rect2 region = tileset->tile_get_region(get_current_tile());
-							region.size = size;
-							coord.x *= (spacing + size.x);
-							coord.y *= (spacing + size.y);
-							region.position += coord;
-							workspace->update();
+							undo_redo->create_action(TTR("Set Tile Icon"));
+							undo_redo->add_do_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), coord);
+							undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), tileset->autotile_get_icon_coordinate(get_current_tile()));
+							undo_redo->add_do_method(workspace, "update");
+							undo_redo->add_undo_method(workspace, "update");
+							undo_redo->commit_action();
 						}
 					}
 				} break;
@@ -1152,14 +1196,22 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 										}
 									}
 								}
-								uint16_t mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
+
+								uint16_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
+								uint16_t new_mask = old_mask;
 								if (erasing) {
-									mask &= ~bit;
+									new_mask &= ~bit;
 								} else {
-									mask |= bit;
+									new_mask |= bit;
+								}
+								if (old_mask != new_mask) {
+									undo_redo->create_action(TTR("Edit Tile Bitmask"));
+									undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask);
+									undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask);
+									undo_redo->add_do_method(workspace, "update");
+									undo_redo->add_undo_method(workspace, "update");
+									undo_redo->commit_action();
 								}
-								tileset->autotile_set_bitmask(get_current_tile(), coord, mask);
-								workspace->update();
 							}
 						} else {
 							if ((erasing && mb->get_button_index() == BUTTON_RIGHT) || (!erasing && mb->get_button_index() == BUTTON_LEFT)) {
@@ -1215,14 +1267,22 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 									}
 								}
 							}
-							uint16_t mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
+
+							uint16_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
+							uint16_t new_mask = old_mask;
 							if (erasing) {
-								mask &= ~bit;
+								new_mask &= ~bit;
 							} else {
-								mask |= bit;
+								new_mask |= bit;
+							}
+							if (old_mask != new_mask) {
+								undo_redo->create_action(TTR("Edit Tile Bitmask"));
+								undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask);
+								undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask);
+								undo_redo->add_do_method(workspace, "update");
+								undo_redo->add_undo_method(workspace, "update");
+								undo_redo->commit_action();
 							}
-							tileset->autotile_set_bitmask(get_current_tile(), coord, mask);
-							workspace->update();
 						}
 					}
 				} break;
@@ -1237,13 +1297,14 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 						shape_anchor.x *= (size.x + spacing);
 						shape_anchor.y *= (size.y + spacing);
 					}
+					const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
 					shape_anchor += current_tile_region.position;
 					if (tools[TOOL_SELECT]->is_pressed()) {
 						if (mb.is_valid()) {
 							if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
 								if (edit_mode != EDITMODE_PRIORITY && current_shape.size() > 0) {
 									for (int i = 0; i < current_shape.size(); i++) {
-										if ((current_shape[i] - mb->get_position()).length_squared() <= MIN_DISTANCE_SQUARED) {
+										if ((current_shape[i] - mb->get_position()).length_squared() <= grab_threshold) {
 											dragging_point = i;
 											workspace->update();
 											return;
@@ -1254,20 +1315,7 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 									Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
 									if (edited_shape_coord != coord) {
 										edited_shape_coord = coord;
-										edited_occlusion_shape = tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord);
-										edited_navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord);
-										Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile());
-										bool found_collision_shape = false;
-										for (int i = 0; i < sd.size(); i++) {
-											if (sd[i].autotile_coord == coord) {
-												edited_collision_shape = sd[i].shape;
-												found_collision_shape = true;
-												break;
-											}
-										}
-										if (!found_collision_shape)
-											edited_collision_shape = Ref<ConvexPolygonShape2D>(NULL);
-										select_coord(edited_shape_coord);
+										_select_edited_shape_coord();
 									}
 								}
 								workspace->update();
@@ -1286,9 +1334,12 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 											points.push_back(p - shape_anchor);
 										}
 
-										edited_collision_shape->set_points(points);
-
-										workspace->update();
+										undo_redo->create_action(TTR("Edit Collision Polygon"));
+										undo_redo->add_do_method(edited_collision_shape.ptr(), "set_points", points);
+										undo_redo->add_undo_method(edited_collision_shape.ptr(), "set_points", edited_collision_shape->get_points());
+										undo_redo->add_do_method(this, "_select_edited_shape_coord");
+										undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+										undo_redo->commit_action();
 									}
 								} else if (edit_mode == EDITMODE_OCCLUSION) {
 									if (dragging_point >= 0) {
@@ -1303,9 +1354,13 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 										}
 
 										w = PoolVector<Vector2>::Write();
-										edited_occlusion_shape->set_polygon(polygon);
 
-										workspace->update();
+										undo_redo->create_action(TTR("Edit Occlusion Polygon"));
+										undo_redo->add_do_method(edited_occlusion_shape.ptr(), "set_polygon", polygon);
+										undo_redo->add_undo_method(edited_occlusion_shape.ptr(), "set_polygon", edited_occlusion_shape->get_polygon());
+										undo_redo->add_do_method(this, "_select_edited_shape_coord");
+										undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+										undo_redo->commit_action();
 									}
 								} else if (edit_mode == EDITMODE_NAVIGATION) {
 									if (dragging_point >= 0) {
@@ -1322,10 +1377,15 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 										}
 
 										w = PoolVector<Vector2>::Write();
-										edited_navigation_shape->set_vertices(polygon);
-										edited_navigation_shape->add_polygon(indices);
 
-										workspace->update();
+										undo_redo->create_action(TTR("Edit Navigation Polygon"));
+										undo_redo->add_do_method(edited_navigation_shape.ptr(), "set_vertices", polygon);
+										undo_redo->add_undo_method(edited_navigation_shape.ptr(), "set_vertices", edited_navigation_shape->get_vertices());
+										undo_redo->add_do_method(edited_navigation_shape.ptr(), "add_polygon", indices);
+										undo_redo->add_undo_method(edited_navigation_shape.ptr(), "add_polygon", edited_navigation_shape->get_polygon(0));
+										undo_redo->add_do_method(this, "_select_edited_shape_coord");
+										undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+										undo_redo->commit_action();
 									}
 								}
 							}
@@ -1336,14 +1396,13 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 							}
 						}
 					} else if (tools[SHAPE_NEW_POLYGON]->is_pressed()) {
-
 						if (mb.is_valid()) {
 							if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
 								Vector2 pos = mb->get_position();
 								pos = snap_point(pos);
 								if (creating_shape) {
 									if (current_shape.size() > 0) {
-										if ((pos - current_shape[0]).length_squared() <= MIN_DISTANCE_SQUARED) {
+										if ((pos - current_shape[0]).length_squared() <= grab_threshold) {
 											if (current_shape.size() > 2) {
 												close_shape(shape_anchor);
 												workspace->update();
@@ -1354,60 +1413,16 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 									current_shape.push_back(pos);
 									workspace->update();
 								} else {
-									int t_id = get_current_tile();
-									if (t_id >= 0) {
-										if (edit_mode == EDITMODE_COLLISION) {
-											Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(t_id);
-											for (int i = 0; i < sd.size(); i++) {
-												if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE || sd[i].autotile_coord == edited_shape_coord) {
-													Ref<ConvexPolygonShape2D> shape = sd[i].shape;
-
-													if (!shape.is_null()) {
-														sd.remove(i);
-														tileset->tile_set_shapes(get_current_tile(), sd);
-														edited_collision_shape = Ref<Shape2D>();
-														workspace->update();
-													}
-													break;
-												}
-											}
-										} else if (edit_mode == EDITMODE_OCCLUSION) {
-											if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
-												Map<Vector2, Ref<OccluderPolygon2D> > map = tileset->autotile_get_light_oclusion_map(t_id);
-												for (Map<Vector2, Ref<OccluderPolygon2D> >::Element *E = map.front(); E; E = E->next()) {
-													if (E->key() == edited_shape_coord) {
-														tileset->autotile_set_light_occluder(get_current_tile(), Ref<OccluderPolygon2D>(), edited_shape_coord);
-														break;
-													}
-												}
-											} else
-												tileset->tile_set_light_occluder(t_id, Ref<OccluderPolygon2D>());
-
-											edited_occlusion_shape = Ref<OccluderPolygon2D>();
-											workspace->update();
-										} else if (edit_mode == EDITMODE_NAVIGATION) {
-											if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
-												Map<Vector2, Ref<NavigationPolygon> > map = tileset->autotile_get_navigation_map(t_id);
-												for (Map<Vector2, Ref<NavigationPolygon> >::Element *E = map.front(); E; E = E->next()) {
-													if (E->key() == edited_shape_coord) {
-														tileset->autotile_set_navigation_polygon(t_id, Ref<NavigationPolygon>(), edited_shape_coord);
-														break;
-													}
-												}
-											} else
-												tileset->tile_set_navigation_polygon(t_id, Ref<NavigationPolygon>());
-											edited_navigation_shape = Ref<NavigationPolygon>();
-											workspace->update();
-										}
-									}
-
 									creating_shape = true;
 									current_shape.resize(0);
 									current_shape.push_back(snap_point(pos));
+									workspace->update();
 								}
-							} else if (mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT && current_shape.size() > 2) {
+							} else if (mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) {
 								if (creating_shape) {
-									close_shape(shape_anchor);
+									creating_shape = false;
+									_select_edited_shape_coord();
+									workspace->update();
 								}
 							}
 						} else if (mm.is_valid()) {
@@ -1427,14 +1442,27 @@ void TileSetEditor::_on_tool_clicked(int p_tool) {
 	if (p_tool == BITMASK_COPY) {
 		bitmask_map_copy = tileset->autotile_get_bitmask_map(get_current_tile());
 	} else if (p_tool == BITMASK_PASTE) {
-		tileset->autotile_clear_bitmask_map(get_current_tile());
+		undo_redo->create_action(TTR("Paste Tile Bitmask"));
+		undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
+		undo_redo->add_undo_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
 		for (Map<Vector2, uint16_t>::Element *E = bitmask_map_copy.front(); E; E = E->next()) {
-			tileset->autotile_set_bitmask(get_current_tile(), E->key(), E->value());
+			undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
 		}
-		workspace->update();
+		for (Map<Vector2, uint16_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) {
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
+		}
+		undo_redo->add_do_method(workspace, "update");
+		undo_redo->add_undo_method(workspace, "update");
+		undo_redo->commit_action();
 	} else if (p_tool == BITMASK_CLEAR) {
-		tileset->autotile_clear_bitmask_map(get_current_tile());
-		workspace->update();
+		undo_redo->create_action(TTR("Clear Tile Bitmask"));
+		undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
+		for (Map<Vector2, uint16_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) {
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
+		}
+		undo_redo->add_do_method(workspace, "update");
+		undo_redo->add_undo_method(workspace, "update");
+		undo_redo->commit_action();
 	} else if (p_tool == SHAPE_DELETE) {
 		if (creating_shape) {
 			creating_shape = false;
@@ -1443,11 +1471,17 @@ void TileSetEditor::_on_tool_clicked(int p_tool) {
 		} else {
 			switch (edit_mode) {
 				case EDITMODE_REGION: {
-					if (workspace_mode == WORKSPACE_EDIT && get_current_tile() >= 0) {
-						tileset->remove_tile(get_current_tile());
-						set_current_tile(-1);
-						workspace->update();
-						workspace_overlay->update();
+					int t_id = get_current_tile();
+					if (workspace_mode == WORKSPACE_EDIT && t_id >= 0) {
+						undo_redo->create_action(TTR("Remove Tile"));
+						undo_redo->add_do_method(tileset.ptr(), "remove_tile", t_id);
+						_undo_tile_removal(t_id);
+						undo_redo->add_do_method(this, "_validate_current_tile_id");
+						undo_redo->add_do_method(workspace, "update");
+						undo_redo->add_undo_method(workspace, "update");
+						undo_redo->add_do_method(workspace_overlay, "update");
+						undo_redo->add_undo_method(workspace_overlay, "update");
+						undo_redo->commit_action();
 					}
 					tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
 					workspace_mode = WORKSPACE_EDIT;
@@ -1455,37 +1489,50 @@ void TileSetEditor::_on_tool_clicked(int p_tool) {
 				} break;
 				case EDITMODE_COLLISION: {
 					if (!edited_collision_shape.is_null()) {
-						Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile());
-						int index = -1;
+						// Necessary to get the version that returns a Array instead of a Vector.
+						Array sd = tileset->call("tile_get_shapes", get_current_tile());
 						for (int i = 0; i < sd.size(); i++) {
-							if (sd[i].shape == edited_collision_shape) {
-								index = i;
+							if (sd[i].get("shape") == edited_collision_shape) {
+								undo_redo->create_action(TTR("Remove Collision Polygon"));
+								undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate());
+								sd.remove(i);
+								undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd);
+								undo_redo->add_do_method(this, "_select_edited_shape_coord");
+								undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+								undo_redo->commit_action();
 								break;
 							}
 						}
-						if (index >= 0) {
-							sd.remove(index);
-							tileset->tile_set_shapes(get_current_tile(), sd);
-							edited_collision_shape = Ref<Shape2D>();
-							current_shape.resize(0);
-							workspace->update();
+					}
+				} break;
+				case EDITMODE_OCCLUSION: {
+					if (!edited_occlusion_shape.is_null()) {
+						undo_redo->create_action(TTR("Remove Occlusion Polygon"));
+						if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
+							undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), Ref<OccluderPolygon2D>());
+							undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile()));
+						} else {
+							undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), Ref<OccluderPolygon2D>(), edited_shape_coord);
+							undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord);
 						}
+						undo_redo->add_do_method(this, "_select_edited_shape_coord");
+						undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+						undo_redo->commit_action();
 					}
 				} break;
 				case EDITMODE_NAVIGATION: {
 					if (!edited_navigation_shape.is_null()) {
-						tileset->autotile_set_navigation_polygon(get_current_tile(), Ref<NavigationPolygon>(), edited_shape_coord);
-						edited_navigation_shape = Ref<NavigationPolygon>();
-						current_shape.resize(0);
-						workspace->update();
-					}
-				} break;
-				case EDITMODE_OCCLUSION: {
-					if (!edited_occlusion_shape.is_null()) {
-						tileset->autotile_set_light_occluder(get_current_tile(), Ref<OccluderPolygon2D>(), edited_shape_coord);
-						edited_occlusion_shape = Ref<OccluderPolygon2D>();
-						current_shape.resize(0);
-						workspace->update();
+						undo_redo->create_action(TTR("Remove Navigation Polygon"));
+						if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
+							undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), Ref<NavigationPolygon>());
+							undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile()));
+						} else {
+							undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), Ref<NavigationPolygon>(), edited_shape_coord);
+							undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord);
+						}
+						undo_redo->add_do_method(this, "_select_edited_shape_coord");
+						undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+						undo_redo->commit_action();
 					}
 				} break;
 				default: {}
@@ -1502,13 +1549,27 @@ void TileSetEditor::_on_tool_clicked(int p_tool) {
 }
 
 void TileSetEditor::_on_priority_changed(float val) {
-	tileset->autotile_set_subtile_priority(get_current_tile(), edited_shape_coord, (int)val);
-	workspace->update();
+	if ((int)val == tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord))
+		return;
+
+	undo_redo->create_action(TTR("Edit Tile Priority"));
+	undo_redo->add_do_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, (int)val);
+	undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord));
+	undo_redo->add_do_method(workspace, "update");
+	undo_redo->add_undo_method(workspace, "update");
+	undo_redo->commit_action();
 }
 
 void TileSetEditor::_on_z_index_changed(float val) {
-	tileset->autotile_set_z_index(get_current_tile(), edited_shape_coord, (int)val);
-	workspace->update();
+	if ((int)val == tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord))
+		return;
+
+	undo_redo->create_action(TTR("Edit Tile Z Index"));
+	undo_redo->add_do_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, (int)val);
+	undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord));
+	undo_redo->add_do_method(workspace, "update");
+	undo_redo->add_undo_method(workspace, "update");
+	undo_redo->commit_action();
 }
 
 void TileSetEditor::_on_grid_snap_toggled(bool p_val) {
@@ -1534,6 +1595,63 @@ void TileSetEditor::_set_snap_sep(Vector2 p_val) {
 	workspace->update();
 }
 
+void TileSetEditor::_validate_current_tile_id() {
+	if (get_current_tile() >= 0 && !tileset->has_tile(get_current_tile()))
+		set_current_tile(-1);
+}
+
+void TileSetEditor::_select_edited_shape_coord() {
+	select_coord(edited_shape_coord);
+}
+
+void TileSetEditor::_undo_tile_removal(int p_id) {
+	undo_redo->add_undo_method(tileset.ptr(), "create_tile", p_id);
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_name", p_id, tileset->tile_get_name(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_normal_map", p_id, tileset->tile_get_normal_map(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture_offset", p_id, tileset->tile_get_texture_offset(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_material", p_id, tileset->tile_get_material(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_modulate", p_id, tileset->tile_get_modulate(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_occluder_offset", p_id, tileset->tile_get_occluder_offset(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon_offset", p_id, tileset->tile_get_navigation_polygon_offset(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_offset", p_id, 0, tileset->tile_get_shape_offset(p_id, 0));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_transform", p_id, 0, tileset->tile_get_shape_transform(p_id, 0));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_z_index", p_id, tileset->tile_get_z_index(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture", p_id, tileset->tile_get_texture(p_id));
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", p_id, tileset->tile_get_region(p_id));
+	// Necessary to get the version that returns a Array instead of a Vector.
+	undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", p_id, tileset->call("tile_get_shapes", p_id));
+	if (tileset->tile_get_tile_mode(p_id) == TileSet::SINGLE_TILE) {
+		undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", p_id, tileset->tile_get_light_occluder(p_id));
+		undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", p_id, tileset->tile_get_navigation_polygon(p_id));
+	} else {
+		Map<Vector2, Ref<OccluderPolygon2D> > oclusion_map = tileset->autotile_get_light_oclusion_map(p_id);
+		for (Map<Vector2, Ref<OccluderPolygon2D> >::Element *E = oclusion_map.front(); E; E = E->next()) {
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", p_id, E->value(), E->key());
+		}
+		Map<Vector2, Ref<NavigationPolygon> > navigation_map = tileset->autotile_get_navigation_map(p_id);
+		for (Map<Vector2, Ref<NavigationPolygon> >::Element *E = navigation_map.front(); E; E = E->next()) {
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", p_id, E->value(), E->key());
+		}
+		Map<Vector2, uint16_t> bitmask_map = tileset->autotile_get_bitmask_map(p_id);
+		for (Map<Vector2, uint16_t>::Element *E = bitmask_map.front(); E; E = E->next()) {
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", p_id, E->key(), E->value());
+		}
+		Map<Vector2, int> priority_map = tileset->autotile_get_priority_map(p_id);
+		for (Map<Vector2, int>::Element *E = priority_map.front(); E; E = E->next()) {
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", p_id, E->key(), E->value());
+		}
+		undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", p_id, tileset->autotile_get_icon_coordinate(p_id));
+		Map<Vector2, int> z_map = tileset->autotile_get_z_index_map(p_id);
+		for (Map<Vector2, int>::Element *E = z_map.front(); E; E = E->next()) {
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", p_id, E->key(), E->value());
+		}
+		undo_redo->add_undo_method(tileset.ptr(), "tile_set_tile_mode", p_id, tileset->tile_get_tile_mode(p_id));
+		undo_redo->add_undo_method(tileset.ptr(), "autotile_set_size", p_id, tileset->autotile_get_size(p_id));
+		undo_redo->add_undo_method(tileset.ptr(), "autotile_set_spacing", p_id, tileset->autotile_get_spacing(p_id));
+		undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask_mode", p_id, tileset->autotile_get_bitmask_mode(p_id));
+	}
+}
+
 void TileSetEditor::_zoom_in() {
 	float scale = workspace->get_scale().x;
 	if (scale < max_scale) {
@@ -1544,7 +1662,6 @@ void TileSetEditor::_zoom_in() {
 	}
 }
 void TileSetEditor::_zoom_out() {
-
 	float scale = workspace->get_scale().x;
 	if (scale > min_scale) {
 		scale /= scale_ratio;
@@ -1769,7 +1886,7 @@ void TileSetEditor::draw_polygon_shapes() {
 					}
 					Vector<Vector2> polygon;
 					Vector<Color> colors;
-					if (shape == edited_collision_shape && current_shape.size() > 2) {
+					if (!creating_shape && shape == edited_collision_shape && current_shape.size() > 2) {
 						for (int j = 0; j < current_shape.size(); j++) {
 							polygon.push_back(current_shape[j]);
 							colors.push_back(c_bg);
@@ -1785,10 +1902,12 @@ void TileSetEditor::draw_polygon_shapes() {
 					}
 
 					if (coord == edited_shape_coord || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
-						for (int j = 0; j < polygon.size() - 1; j++) {
-							workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+						if (!creating_shape) {
+							for (int j = 0; j < polygon.size() - 1; j++) {
+								workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+							}
+							workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 						}
-						workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 						if (shape == edited_collision_shape) {
 							draw_handles = true;
 						}
@@ -1807,7 +1926,7 @@ void TileSetEditor::draw_polygon_shapes() {
 					Vector<Color> colors;
 					Vector2 anchor = WORKSPACE_MARGIN;
 					anchor += tileset->tile_get_region(get_current_tile()).position;
-					if (shape == edited_occlusion_shape && current_shape.size() > 2) {
+					if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) {
 						for (int j = 0; j < current_shape.size(); j++) {
 							polygon.push_back(current_shape[j]);
 							colors.push_back(c_bg);
@@ -1820,10 +1939,12 @@ void TileSetEditor::draw_polygon_shapes() {
 					}
 					workspace->draw_polygon(polygon, colors);
 
-					for (int j = 0; j < polygon.size() - 1; j++) {
-						workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+					if (!creating_shape) {
+						for (int j = 0; j < polygon.size() - 1; j++) {
+							workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+						}
+						workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 					}
-					workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 					if (shape == edited_occlusion_shape) {
 						draw_handles = true;
 					}
@@ -1852,7 +1973,7 @@ void TileSetEditor::draw_polygon_shapes() {
 						}
 						Vector<Vector2> polygon;
 						Vector<Color> colors;
-						if (shape == edited_occlusion_shape && current_shape.size() > 2) {
+						if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) {
 							for (int j = 0; j < current_shape.size(); j++) {
 								polygon.push_back(current_shape[j]);
 								colors.push_back(c_bg);
@@ -1866,10 +1987,12 @@ void TileSetEditor::draw_polygon_shapes() {
 						workspace->draw_polygon(polygon, colors);
 
 						if (coord == edited_shape_coord) {
-							for (int j = 0; j < polygon.size() - 1; j++) {
-								workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+							if (!creating_shape) {
+								for (int j = 0; j < polygon.size() - 1; j++) {
+									workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+								}
+								workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 							}
-							workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 							if (shape == edited_occlusion_shape) {
 								draw_handles = true;
 							}
@@ -1890,7 +2013,7 @@ void TileSetEditor::draw_polygon_shapes() {
 					Vector<Color> colors;
 					Vector2 anchor = WORKSPACE_MARGIN;
 					anchor += tileset->tile_get_region(get_current_tile()).position;
-					if (shape == edited_navigation_shape && current_shape.size() > 2) {
+					if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) {
 						for (int j = 0; j < current_shape.size(); j++) {
 							polygon.push_back(current_shape[j]);
 							colors.push_back(c_bg);
@@ -1904,10 +2027,12 @@ void TileSetEditor::draw_polygon_shapes() {
 					}
 					workspace->draw_polygon(polygon, colors);
 
-					for (int j = 0; j < polygon.size() - 1; j++) {
-						workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+					if (!creating_shape) {
+						for (int j = 0; j < polygon.size() - 1; j++) {
+							workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+						}
+						workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 					}
-					workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 					if (shape == edited_navigation_shape) {
 						draw_handles = true;
 					}
@@ -1936,7 +2061,7 @@ void TileSetEditor::draw_polygon_shapes() {
 						}
 						Vector<Vector2> polygon;
 						Vector<Color> colors;
-						if (shape == edited_navigation_shape && current_shape.size() > 2) {
+						if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) {
 							for (int j = 0; j < current_shape.size(); j++) {
 								polygon.push_back(current_shape[j]);
 								colors.push_back(c_bg);
@@ -1951,10 +2076,12 @@ void TileSetEditor::draw_polygon_shapes() {
 						workspace->draw_polygon(polygon, colors);
 
 						if (coord == edited_shape_coord) {
-							for (int j = 0; j < polygon.size() - 1; j++) {
-								workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+							if (!creating_shape) {
+								for (int j = 0; j < polygon.size() - 1; j++) {
+									workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true);
+								}
+								workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 							}
-							workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true);
 							if (shape == edited_navigation_shape) {
 								draw_handles = true;
 							}
@@ -1971,6 +2098,7 @@ void TileSetEditor::draw_polygon_shapes() {
 			workspace->draw_line(current_shape[j], current_shape[j + 1], Color(0, 1, 1), 1, true);
 		}
 		workspace->draw_line(current_shape[current_shape.size() - 1], snap_point(workspace->get_local_mouse_position()), Color(0, 1, 1), 1, true);
+		draw_handles = true;
 	}
 }
 
@@ -1999,16 +2127,29 @@ void TileSetEditor::close_shape(const Vector2 &shape_anchor) {
 
 			shape->set_points(segments);
 
+			undo_redo->create_action(TTR("Create Collision Polygon"));
+			// Necessary to get the version that returns a Array instead of a Vector.
+			Array sd = tileset->call("tile_get_shapes", get_current_tile());
+			undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate());
+			for (int i = 0; i < sd.size(); i++) {
+				if (sd[i].get("shape") == edited_collision_shape) {
+					sd.remove(i);
+					break;
+				}
+			}
+			undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd);
 			if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE)
-				tileset->tile_add_shape(get_current_tile(), shape, Transform2D(), false, edited_shape_coord);
+				undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D(), false, edited_shape_coord);
 			else
-				tileset->tile_add_shape(get_current_tile(), shape, Transform2D());
-
-			edited_collision_shape = shape;
+				undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D());
+			tools[TOOL_SELECT]->set_pressed(true);
+			undo_redo->add_do_method(this, "_select_edited_shape_coord");
+			undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+			undo_redo->commit_action();
+		} else {
+			tools[TOOL_SELECT]->set_pressed(true);
+			workspace->update();
 		}
-
-		tools[TOOL_SELECT]->set_pressed(true);
-		workspace->update();
 	} else if (edit_mode == EDITMODE_OCCLUSION) {
 		Ref<OccluderPolygon2D> shape = memnew(OccluderPolygon2D);
 
@@ -2023,13 +2164,18 @@ void TileSetEditor::close_shape(const Vector2 &shape_anchor) {
 		w = PoolVector<Vector2>::Write();
 		shape->set_polygon(polygon);
 
-		if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE)
-			tileset->autotile_set_light_occluder(get_current_tile(), shape, edited_shape_coord);
-		else
-			tileset->tile_set_light_occluder(get_current_tile(), shape);
-		edited_occlusion_shape = shape;
+		undo_redo->create_action(TTR("Create Occlusion Polygon"));
+		if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
+			undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), shape, edited_shape_coord);
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord);
+		} else {
+			undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), shape);
+			undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile()));
+		}
 		tools[TOOL_SELECT]->set_pressed(true);
-		workspace->update();
+		undo_redo->add_do_method(this, "_select_edited_shape_coord");
+		undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+		undo_redo->commit_action();
 	} else if (edit_mode == EDITMODE_NAVIGATION) {
 		Ref<NavigationPolygon> shape = memnew(NavigationPolygon);
 
@@ -2047,13 +2193,18 @@ void TileSetEditor::close_shape(const Vector2 &shape_anchor) {
 		shape->set_vertices(polygon);
 		shape->add_polygon(indices);
 
-		if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE)
-			tileset->autotile_set_navigation_polygon(get_current_tile(), shape, edited_shape_coord);
-		else
-			tileset->tile_set_navigation_polygon(get_current_tile(), shape);
-		edited_navigation_shape = shape;
+		undo_redo->create_action(TTR("Create Navigation Polygon"));
+		if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
+			undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), shape, edited_shape_coord);
+			undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord);
+		} else {
+			undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), shape);
+			undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile()));
+		}
 		tools[TOOL_SELECT]->set_pressed(true);
-		workspace->update();
+		undo_redo->add_do_method(this, "_select_edited_shape_coord");
+		undo_redo->add_undo_method(this, "_select_edited_shape_coord");
+		undo_redo->commit_action();
 	}
 	tileset->_change_notify("");
 }
@@ -2098,6 +2249,23 @@ void TileSetEditor::select_coord(const Vector2 &coord) {
 			}
 		}
 	} else {
+		Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile());
+		bool found_collision_shape = false;
+		for (int i = 0; i < sd.size(); i++) {
+			if (sd[i].autotile_coord == coord) {
+				if (edited_collision_shape != sd[i].shape)
+					edited_collision_shape = sd[i].shape;
+				found_collision_shape = true;
+				break;
+			}
+		}
+		if (!found_collision_shape)
+			edited_collision_shape = Ref<ConvexPolygonShape2D>(NULL);
+		if (edited_occlusion_shape != tileset->autotile_get_light_occluder(get_current_tile(), coord))
+			edited_occlusion_shape = tileset->autotile_get_light_occluder(get_current_tile(), coord);
+		if (edited_navigation_shape != tileset->autotile_get_navigation_polygon(get_current_tile(), coord))
+			edited_navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord);
+
 		int spacing = tileset->autotile_get_spacing(get_current_tile());
 		Vector2 size = tileset->autotile_get_size(get_current_tile());
 		Vector2 shape_anchor = coord;
@@ -2168,6 +2336,24 @@ Vector2 TileSetEditor::snap_point(const Vector2 &point) {
 	return p;
 }
 
+void TileSetEditor::add_texture(Ref<Texture> p_texture) {
+	texture_list->add_item(p_texture->get_path().get_file());
+	texture_map.insert(p_texture->get_rid(), p_texture);
+	texture_list->set_item_metadata(texture_list->get_item_count() - 1, p_texture->get_rid());
+}
+
+void TileSetEditor::remove_texture(Ref<Texture> p_texture) {
+	texture_list->remove_item(texture_list->find_metadata(p_texture->get_rid()));
+	texture_map.erase(p_texture->get_rid());
+
+	_validate_current_tile_id();
+
+	if (!get_current_texture().is_valid()) {
+		_on_texture_list_selected(-1);
+		workspace_overlay->update();
+	}
+}
+
 void TileSetEditor::update_texture_list() {
 	Ref<Texture> selected_texture = get_current_texture();
 
@@ -2184,9 +2370,7 @@ void TileSetEditor::update_texture_list() {
 		}
 
 		if (!texture_map.has(tileset->tile_get_texture(E->get())->get_rid())) {
-			texture_list->add_item(tileset->tile_get_texture(E->get())->get_path().get_file());
-			texture_map.insert(tileset->tile_get_texture(E->get())->get_rid(), tileset->tile_get_texture(E->get()));
-			texture_list->set_item_metadata(texture_list->get_item_count() - 1, tileset->tile_get_texture(E->get())->get_rid());
+			add_texture(tileset->tile_get_texture(E->get()));
 		}
 	}
 	for (int i = 0; i < ids_to_remove.size(); i++) {
@@ -2200,7 +2384,9 @@ void TileSetEditor::update_texture_list() {
 	} else if (get_current_texture().is_valid()) {
 		_on_texture_list_selected(texture_list->find_metadata(get_current_texture()->get_rid()));
 	} else {
+		_validate_current_tile_id();
 		_on_texture_list_selected(-1);
+		workspace_overlay->update();
 	}
 	update_texture_list_icon();
 	helper->_change_notify("");
@@ -2276,7 +2462,7 @@ void TileSetEditor::update_workspace_tile_mode() {
 		if (edit_mode == EDITMODE_ICON)
 			select_coord(tileset->autotile_get_icon_coordinate(get_current_tile()));
 		else
-			select_coord(edited_shape_coord);
+			_select_edited_shape_coord();
 	} else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
 		if (tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed()) {
 			tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
@@ -2285,7 +2471,7 @@ void TileSetEditor::update_workspace_tile_mode() {
 		if (edit_mode == EDITMODE_ICON)
 			select_coord(tileset->autotile_get_icon_coordinate(get_current_tile()));
 		else
-			select_coord(edited_shape_coord);
+			_select_edited_shape_coord();
 
 		tool_editmode[EDITMODE_BITMASK]->hide();
 		tool_editmode[EDITMODE_PRIORITY]->hide();

+ 9 - 0
editor/plugins/tile_set_editor_plugin.h

@@ -94,6 +94,7 @@ class TileSetEditor : public HSplitContainer {
 	Ref<TileSet> tileset;
 	TilesetEditorContext *helper;
 	EditorNode *editor;
+	UndoRedo *undo_redo;
 
 	ConfirmationDialog *cd;
 	AcceptDialog *err_dialog;
@@ -151,10 +152,14 @@ class TileSetEditor : public HSplitContainer {
 	void update_texture_list();
 	void update_texture_list_icon();
 
+	void add_texture(Ref<Texture> p_texture);
+	void remove_texture(Ref<Texture> p_texture);
+
 	Ref<Texture> get_current_texture();
 
 	static void _import_node(Node *p_node, Ref<TileSet> p_library);
 	static void _import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_merge);
+	void _undo_redo_import_scene(Node *p_scene, bool p_merge);
 
 protected:
 	static void _bind_methods();
@@ -186,6 +191,10 @@ private:
 	void _set_snap_off(Vector2 p_val);
 	void _set_snap_sep(Vector2 p_val);
 
+	void _validate_current_tile_id();
+	void _select_edited_shape_coord();
+	void _undo_tile_removal(int p_id);
+
 	void _zoom_in();
 	void _zoom_out();
 	void _zoom_reset();

+ 24 - 2
scene/resources/tile_set.cpp

@@ -507,6 +507,13 @@ int TileSet::autotile_get_subtile_priority(int p_id, const Vector2 &p_coord) {
 	return 1;
 }
 
+const Map<Vector2, int> &TileSet::autotile_get_priority_map(int p_id) const {
+
+	static Map<Vector2, int> dummy;
+	ERR_FAIL_COND_V(!tile_map.has(p_id), dummy);
+	return tile_map[p_id].autotile_data.priority_map;
+}
+
 void TileSet::autotile_set_z_index(int p_id, const Vector2 &p_coord, int p_z_index) {
 
 	ERR_FAIL_COND(!tile_map.has(p_id));
@@ -524,11 +531,11 @@ int TileSet::autotile_get_z_index(int p_id, const Vector2 &p_coord) {
 	return 0;
 }
 
-const Map<Vector2, int> &TileSet::autotile_get_priority_map(int p_id) const {
+const Map<Vector2, int> &TileSet::autotile_get_z_index_map(int p_id) const {
 
 	static Map<Vector2, int> dummy;
 	ERR_FAIL_COND_V(!tile_map.has(p_id), dummy);
-	return tile_map[p_id].autotile_data.priority_map;
+	return tile_map[p_id].autotile_data.z_index_map;
 }
 
 void TileSet::autotile_set_bitmask(int p_id, Vector2 p_coord, uint16_t p_flag) {
@@ -986,8 +993,23 @@ void TileSet::clear() {
 void TileSet::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("create_tile", "id"), &TileSet::create_tile);
+	ClassDB::bind_method(D_METHOD("autotile_clear_bitmask_map", "id"), &TileSet::autotile_clear_bitmask_map);
+	ClassDB::bind_method(D_METHOD("autotile_set_icon_coordinate", "id", "coord"), &TileSet::autotile_set_icon_coordinate);
+	ClassDB::bind_method(D_METHOD("autotile_get_icon_coordinate", "id"), &TileSet::autotile_get_icon_coordinate);
+	ClassDB::bind_method(D_METHOD("autotile_set_subtile_priority", "id", "coord", "priority"), &TileSet::autotile_set_subtile_priority);
+	ClassDB::bind_method(D_METHOD("autotile_get_subtile_priority", "id", "coord"), &TileSet::autotile_get_subtile_priority);
+	ClassDB::bind_method(D_METHOD("autotile_set_z_index", "id", "coord", "z_index"), &TileSet::autotile_set_z_index);
+	ClassDB::bind_method(D_METHOD("autotile_get_z_index", "id", "coord"), &TileSet::autotile_get_z_index);
+	ClassDB::bind_method(D_METHOD("autotile_set_light_occluder", "id", "light_occluder", "coord"), &TileSet::autotile_set_light_occluder);
+	ClassDB::bind_method(D_METHOD("autotile_get_light_occluder", "id", "coord"), &TileSet::autotile_get_light_occluder);
+	ClassDB::bind_method(D_METHOD("autotile_set_navigation_polygon", "id", "navigation_polygon", "coord"), &TileSet::autotile_set_navigation_polygon);
+	ClassDB::bind_method(D_METHOD("autotile_get_navigation_polygon", "id", "coord"), &TileSet::autotile_get_navigation_polygon);
+	ClassDB::bind_method(D_METHOD("autotile_set_bitmask", "id", "bitmask", "flag"), &TileSet::autotile_set_bitmask);
+	ClassDB::bind_method(D_METHOD("autotile_get_bitmask", "id", "coord"), &TileSet::autotile_get_bitmask);
 	ClassDB::bind_method(D_METHOD("autotile_set_bitmask_mode", "id", "mode"), &TileSet::autotile_set_bitmask_mode);
 	ClassDB::bind_method(D_METHOD("autotile_get_bitmask_mode", "id"), &TileSet::autotile_get_bitmask_mode);
+	ClassDB::bind_method(D_METHOD("autotile_set_spacing", "id", "spacing"), &TileSet::autotile_set_spacing);
+	ClassDB::bind_method(D_METHOD("autotile_get_spacing", "id"), &TileSet::autotile_get_spacing);
 	ClassDB::bind_method(D_METHOD("autotile_set_size", "id", "size"), &TileSet::autotile_set_size);
 	ClassDB::bind_method(D_METHOD("autotile_get_size", "id"), &TileSet::autotile_get_size);
 	ClassDB::bind_method(D_METHOD("tile_set_name", "id", "name"), &TileSet::tile_set_name);

+ 1 - 0
scene/resources/tile_set.h

@@ -175,6 +175,7 @@ public:
 
 	void autotile_set_z_index(int p_id, const Vector2 &p_coord, int p_z_index);
 	int autotile_get_z_index(int p_id, const Vector2 &p_coord);
+	const Map<Vector2, int> &autotile_get_z_index_map(int p_id) const;
 
 	void autotile_set_bitmask(int p_id, Vector2 p_coord, uint16_t p_flag);
 	uint16_t autotile_get_bitmask(int p_id, Vector2 p_coord);