From 442285d15f55ecd60a436b5759bfaa02b62d7fcc Mon Sep 17 00:00:00 2001 From: Emmanouil Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:27:30 +0200 Subject: [PATCH] Make undo/redo store tilemap cell indices and tileset tiles Fixes issues with cases 0.5 and 5 of auto mode, and should be a better system overall. Only works with BaseDraw tools, needs to be applied everywhere as well. --- src/Autoload/Global.gd | 2 - src/Autoload/OpenSave.gd | 10 ++-- src/Classes/Cels/BaseCel.gd | 4 -- src/Classes/Cels/CelTileMap.gd | 70 +++++++----------------- src/Classes/Project.gd | 2 +- src/Classes/TileSetCustom.gd | 65 +++++++++++----------- src/Tools/BaseDraw.gd | 3 + src/UI/Timeline/NewTileMapLayerDialog.gd | 2 +- 8 files changed, 65 insertions(+), 93 deletions(-) diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 4b373734f..e55f4fef1 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -984,7 +984,6 @@ func undo_or_redo( if action_name == "Scale": cel.size_changed(project.size) canvas.update_texture(layer_index, frame_index, project, undo) - cel.on_undo_redo(undo) else: for i in project.frames.size(): for j in project.layers.size(): @@ -992,7 +991,6 @@ func undo_or_redo( if action_name == "Scale": cel.size_changed(project.size) canvas.update_texture(j, i, project, undo) - cel.on_undo_redo(undo) canvas.selection.queue_redraw() if action_name == "Scale": diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index bb4a17cad..28ff6d169 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -269,7 +269,7 @@ func open_pxo_file(path: String, is_backup := false, replace_empty := true) -> v var image := Image.create_from_data( tile_size.x, tile_size.y, false, new_project.get_image_format(), image_data ) - tileset.add_tile(image, null, 2) + tileset.add_tile(image, null) zip_reader.close() new_project.export_directory_path = path.get_base_dir() @@ -844,14 +844,14 @@ func open_image_as_tileset( var frame_width := image.get_size().x / horiz var frame_height := image.get_size().y / vert var tile_size := Vector2i(frame_width, frame_height) - var tileset := TileSetCustom.new(tile_size, project, path.get_basename().get_file()) + var tileset := TileSetCustom.new(tile_size, path.get_basename().get_file()) for yy in range(vert): for xx in range(horiz): var cropped_image := image.get_region( Rect2i(frame_width * xx, frame_height * yy, frame_width, frame_height) ) @warning_ignore("int_as_enum_without_cast") - tileset.add_tile(cropped_image, null, 2) + tileset.add_tile(cropped_image, null) project.tilesets.append(tileset) @@ -866,7 +866,7 @@ func open_image_as_tileset_smart( if sliced_rects.size() == 0: # Image is empty sprite (manually set data to be consistent) tile_size = image.get_size() sliced_rects.append(Rect2i(Vector2i.ZERO, tile_size)) - var tileset := TileSetCustom.new(tile_size, project, path.get_basename().get_file()) + var tileset := TileSetCustom.new(tile_size, path.get_basename().get_file()) for rect in sliced_rects: var offset: Vector2 = (0.5 * (tile_size - rect.size)).floor() var cropped_image := Image.create( @@ -874,7 +874,7 @@ func open_image_as_tileset_smart( ) cropped_image.blit_rect(image, rect, offset) @warning_ignore("int_as_enum_without_cast") - tileset.add_tile(cropped_image, null, 2) + tileset.add_tile(cropped_image, null) project.tilesets.append(tileset) diff --git a/src/Classes/Cels/BaseCel.gd b/src/Classes/Cels/BaseCel.gd index ec9b9089c..4b01ec7d8 100644 --- a/src/Classes/Cels/BaseCel.gd +++ b/src/Classes/Cels/BaseCel.gd @@ -77,10 +77,6 @@ func update_texture(_undo := false) -> void: cel.texture_changed.emit() -func on_undo_redo(_undo: bool) -> void: - pass - - ## Returns a curated [Dictionary] containing the cel data. func serialize() -> Dictionary: var dict := {"opacity": opacity, "z_index": z_index} diff --git a/src/Classes/Cels/CelTileMap.gd b/src/Classes/Cels/CelTileMap.gd index 135c8755a..8682c3a78 100644 --- a/src/Classes/Cels/CelTileMap.gd +++ b/src/Classes/Cels/CelTileMap.gd @@ -29,9 +29,6 @@ var cells: Array[Cell] var horizontal_cells: int ## The amount of vertical cells. var vertical_cells: int -## Dictionary of [int] and an [Array] of [bool] ([member TileSetPanel.placing_tiles]) -## and [enum TileSetPanel.TileEditingMode]. -var undo_redo_modes := {} ## Dictionary of [int] and [Array]. ## The key is the index of the tile in the tileset, ## and the value is the index of the tilemap tile that changed first, along with @@ -147,14 +144,9 @@ func transform_tile( return transformed_tile -func update_tileset(undo: bool) -> void: +func update_tileset() -> void: editing_images.clear() - var undos := tileset.project.undos - if not undo and not _is_redo(): - undo_redo_modes[undos] = [TileSetPanel.placing_tiles, TileSetPanel.tile_editing_mode] - if undo: - undos += 1 - var tile_editing_mode := _get_tile_editing_mode(undos) + var tile_editing_mode := TileSetPanel.tile_editing_mode for i in cells.size(): var coords := get_cell_coords_in_image(i) var rect := Rect2i(coords, tileset.tile_size) @@ -171,7 +163,7 @@ func update_tileset(undo: bool) -> void: if index == 0: # If the tileset is empty, only then add a new tile. if tileset.tiles.size() <= 1: - tileset.add_tile(image_portion, self, tile_editing_mode) + tileset.add_tile(image_portion, self) cells[i].index = tileset.tiles.size() - 1 continue if not tiles_equal(i, image_portion, current_tile.image): @@ -189,12 +181,8 @@ func update_tileset(undo: bool) -> void: found_tile = true break if not found_tile: - tileset.add_tile(image_portion, self, tile_editing_mode) + tileset.add_tile(image_portion, self) cells[i].index = tileset.tiles.size() - 1 - if undo: - var tile_removed := tileset.remove_unused_tiles(self) - if tile_removed: - _re_index_all_cells() ## Cases:[br] @@ -256,7 +244,7 @@ func _handle_auto_editing_mode(i: int, image_portion: Image) -> void: else: # Case 2: The cell is not mapped already, # and it does not exist in the tileset. - tileset.add_tile(image_portion, self, TileSetPanel.TileEditingMode.AUTO) + tileset.add_tile(image_portion, self) cells[i].index = tileset.tiles.size() - 1 else: # If the cell is already mapped. if tiles_equal(i, image_portion, current_tile.image): @@ -284,7 +272,7 @@ func _handle_auto_editing_mode(i: int, image_portion: Image) -> void: # exist in the tileset as a tile, # and the currently mapped tile still exists in the tileset. tileset.unuse_tile_at_index(index, self) - tileset.add_tile(image_portion, self, TileSetPanel.TileEditingMode.AUTO) + tileset.add_tile(image_portion, self) cells[i].index = tileset.tiles.size() - 1 else: # Case 7: The cell is mapped and it does not @@ -349,15 +337,6 @@ func _is_redo() -> bool: return Global.control.redone -func _get_tile_editing_mode(undos: int) -> TileSetPanel.TileEditingMode: - var tile_editing_mode: TileSetPanel.TileEditingMode - if undo_redo_modes.has(undos): - tile_editing_mode = undo_redo_modes[undos][1] - else: - tile_editing_mode = TileSetPanel.tile_editing_mode - return tile_editing_mode - - ## If the tileset has been modified by another tile, make sure to also update it here. func _on_tileset_updated(cel: CelTileMap) -> void: if cel == self or not is_instance_valid(cel): @@ -415,36 +394,29 @@ func size_changed(new_size: Vector2i) -> void: _re_index_all_cells() -func on_undo_redo(undo: bool) -> void: - var undos := tileset.project.undos - if undo: - undos += 1 - if (undo or _is_redo()) and undo_redo_modes.has(undos): - var placing_tiles: bool = undo_redo_modes[undos][0] - if placing_tiles: - _re_index_all_cells() - return - update_tileset(undo) - - func serialize_undo_data() -> Dictionary: var dict := {} - var cells_serialized := [] - cells_serialized.resize(cells.size()) - for i in cells.size(): - cells_serialized[i] = cells[i].serialize() - dict["cells_data"] = cells_serialized + var cell_indices := [] + cell_indices.resize(cells.size()) + for i in cell_indices.size(): + cell_indices[i] = cells[i].serialize() + dict["cell_indices"] = cell_indices + dict["tileset"] = tileset.serialize_undo_data() return dict func deserialize_undo_data(dict: Dictionary, undo_redo: UndoRedo, undo: bool) -> void: - var cells_data = dict["cells_data"] - for i in cells_data.size(): - var cell_data: Dictionary = cells_data[i] - if undo: + var cell_indices = dict["cell_indices"] + if undo: + for i in cell_indices.size(): + var cell_data: Dictionary = cell_indices[i] undo_redo.add_undo_method(cells[i].deserialize.bind(cell_data)) - else: + undo_redo.add_undo_method(tileset.deserialize_undo_data.bind(dict.get("tileset"), self)) + else: + for i in cell_indices.size(): + var cell_data: Dictionary = cell_indices[i] undo_redo.add_do_method(cells[i].deserialize.bind(cell_data)) + undo_redo.add_do_method(tileset.deserialize_undo_data.bind(dict.get("tileset"), self)) func serialize() -> Dictionary: diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 512bf1310..317369523 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -352,7 +352,7 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces if dict.has("tilesets"): for saved_tileset in dict["tilesets"]: var tile_size = str_to_var("Vector2i" + saved_tileset.get("tile_size")) - var tileset := TileSetCustom.new(tile_size, self) + var tileset := TileSetCustom.new(tile_size) tileset.deserialize(saved_tileset) tilesets.append(tileset) if dict.has("frames") and dict.has("layers"): diff --git a/src/Classes/TileSetCustom.gd b/src/Classes/TileSetCustom.gd index 9a051838c..49c89b714 100644 --- a/src/Classes/TileSetCustom.gd +++ b/src/Classes/TileSetCustom.gd @@ -10,8 +10,6 @@ extends RefCounted ## The [CelTileMap] that the changes are coming from is referenced in the [param cel] parameter. signal updated(cel: CelTileMap) -## The [Project] the tileset is being used by. -var project: Project ## The tileset's name. var name := "" ## The size of each individual tile. @@ -24,45 +22,30 @@ var tiles: Array[Tile] = [] class Tile: ## The [Image] tile itself. var image: Image - ## The mode that was used when this tile was added to the tileset. - var mode_added: TileSetPanel.TileEditingMode ## The amount of tiles this tile is being used in tilemaps. var times_used := 1 - ## The step number of undo/redo when this tile was added to the tileset. - var undo_step_added := 0 - func _init( - _image: Image, _mode_added: TileSetPanel.TileEditingMode, _undo_step_added := 0 - ) -> void: + func _init(_image: Image) -> void: image = _image - mode_added = _mode_added - undo_step_added = _undo_step_added ## A method that checks if the tile should be removed from the tileset. - ## Returns [code]true[/code] if the current undo step is less than [member undo_step_added], - ## which essentially means that the tile always gets removed if the user undos to the point - ## the tile was added to the tileset. - ## Otherwise, it returns [code]true[/code] if [member mode_added] is not equal to - ## [enum TileSetPanel.TileEditingMode.STACK] and the amount of [member times_used] is 0. - func can_be_removed(project: Project) -> bool: - if project.undos < undo_step_added: - return true - return mode_added != TileSetPanel.TileEditingMode.STACK and times_used <= 0 + ## Returns [code]true[/code] if the amount of [member times_used] is 0. + func can_be_removed() -> bool: + return times_used <= 0 -func _init(_tile_size: Vector2i, _project: Project, _name := "") -> void: +func _init(_tile_size: Vector2i, _name := "") -> void: tile_size = _tile_size - project = _project name = _name var empty_image := Image.create_empty(tile_size.x, tile_size.y, false, Image.FORMAT_RGBA8) - tiles.append(Tile.new(empty_image, TileSetPanel.tile_editing_mode)) + tiles.append(Tile.new(empty_image)) ## Adds a new [param image] as a tile to the tileset. ## The [param cel] parameter references the [CelTileMap] that this change is coming from, ## and the [param edit_mode] parameter contains the tile editing mode at the time of this change. -func add_tile(image: Image, cel: CelTileMap, edit_mode: TileSetPanel.TileEditingMode) -> void: - var tile := Tile.new(image, edit_mode, project.undos) +func add_tile(image: Image, cel: CelTileMap) -> void: + var tile := Tile.new(image) tiles.append(tile) updated.emit(cel) @@ -70,10 +53,8 @@ func add_tile(image: Image, cel: CelTileMap, edit_mode: TileSetPanel.TileEditing ## Adds a new [param image] as a tile in a given [param position] in the tileset. ## The [param cel] parameter references the [CelTileMap] that this change is coming from, ## and the [param edit_mode] parameter contains the tile editing mode at the time of this change. -func insert_tile( - image: Image, position: int, cel: CelTileMap, edit_mode: TileSetPanel.TileEditingMode -) -> void: - var tile := Tile.new(image, edit_mode, project.undos) +func insert_tile(image: Image, position: int, cel: CelTileMap) -> void: + var tile := Tile.new(image) tiles.insert(position, tile) updated.emit(cel) @@ -86,7 +67,7 @@ func insert_tile( ## The [param cel] parameter references the [CelTileMap] that this change is coming from. func unuse_tile_at_index(index: int, cel: CelTileMap) -> bool: tiles[index].times_used -= 1 - if tiles[index].can_be_removed(project): + if tiles[index].can_be_removed(): remove_tile_at_index(index, cel) return true return false @@ -122,7 +103,7 @@ func remove_unused_tiles(cel: CelTileMap) -> bool: var tile_removed := false for i in range(tiles.size() - 1, 0, -1): var tile := tiles[i] - if tile.can_be_removed(project): + if tile.can_be_removed(): remove_tile_at_index(i, cel) tile_removed = true return tile_removed @@ -139,3 +120,25 @@ func serialize() -> Dictionary: func deserialize(dict: Dictionary) -> void: name = dict.get("name", name) tile_size = str_to_var("Vector2i" + dict.get("tile_size")) + + +func serialize_undo_data() -> Dictionary: + var dict := {} + for tile in tiles: + var image_data := tile.image.get_data() + dict[tile.image] = [image_data.compress(), image_data.size(), tile.times_used] + return dict + + +func deserialize_undo_data(dict: Dictionary, cel: CelTileMap) -> void: + tiles.resize(dict.size()) + var i := 0 + for image: Image in dict: + var tile_data = dict[image] + var buffer_size := tile_data[1] as int + var image_data := (tile_data[0] as PackedByteArray).decompress(buffer_size) + image.set_data(tile_size.x, tile_size.y, false, image.get_format(), image_data) + tiles[i] = Tile.new(image) + tiles[i].times_used = tile_data[2] + i += 1 + updated.emit(cel) diff --git a/src/Tools/BaseDraw.gd b/src/Tools/BaseDraw.gd index 22f6b592c..5fa176d47 100644 --- a/src/Tools/BaseDraw.gd +++ b/src/Tools/BaseDraw.gd @@ -272,6 +272,9 @@ func prepare_undo(action: String) -> void: func commit_undo() -> void: + for cel in _undo_data: + if cel is CelTileMap: + (cel as CelTileMap).update_tileset() var redo_data := _get_undo_data() var project := Global.current_project var frame := -1 diff --git a/src/UI/Timeline/NewTileMapLayerDialog.gd b/src/UI/Timeline/NewTileMapLayerDialog.gd index 497840d19..0c4020d03 100644 --- a/src/UI/Timeline/NewTileMapLayerDialog.gd +++ b/src/UI/Timeline/NewTileMapLayerDialog.gd @@ -14,7 +14,7 @@ func _on_confirmed() -> void: var tile_size := tile_size_slider.value var tileset: TileSetCustom if tileset_option_button.selected == 0: - tileset = TileSetCustom.new(tile_size, project, tileset_name) + tileset = TileSetCustom.new(tile_size, tileset_name) else: tileset = project.tilesets[tileset_option_button.selected - 1] var layer := LayerTileMap.new(project, tileset, layer_name)