diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index dc9f8f3c2..a710f8dd5 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -258,6 +258,18 @@ func open_pxo_file(path: String, is_backup := false, replace_empty := true) -> v new_project.tiles.tile_mask = image else: new_project.tiles.reset_mask() + if result.has("tilesets"): + for i in result.tilesets.size(): + var tileset_dict: Dictionary = result.tilesets[i] + var tileset := new_project.tilesets[i] + var tile_size := tileset.tile_size + var tile_amount: int = tileset_dict.tile_amount + for j in tile_amount: + var image_data := zip_reader.read_file("tilesets/%s/%s" % [i, j]) + var image := Image.create_from_data( + tile_size.x, tile_size.y, false, new_project.get_image_format(), image_data + ) + tileset.add_tile(image, 2) zip_reader.close() new_project.export_directory_path = path.get_base_dir() @@ -418,6 +430,14 @@ func save_pxo_file( zip_packer.start_file("image_data/tile_map") zip_packer.write_file(project.tiles.tile_mask.get_data()) zip_packer.close_file() + for i in project.tilesets.size(): + var tileset := project.tilesets[i] + var tileset_path := "tilesets/%s" % i + for j in tileset.tiles.size(): + var tile := tileset.tiles[j] + zip_packer.start_file(tileset_path.path_join(str(j))) + zip_packer.write_file(tile.image.get_data()) + zip_packer.close_file() zip_packer.close() if temp_path != path: diff --git a/src/Classes/Cels/CelTileMap.gd b/src/Classes/Cels/CelTileMap.gd index 476bba033..6d77f5f8b 100644 --- a/src/Classes/Cels/CelTileMap.gd +++ b/src/Classes/Cels/CelTileMap.gd @@ -1,7 +1,13 @@ class_name CelTileMap extends PixelCel -var tileset: TileSetCustom +var tileset: TileSetCustom: + set(value): + tileset = value + if is_instance_valid(tileset): + indices_x = ceili(float(get_image().get_width()) / tileset.tile_size.x) + indices_y = ceili(float(get_image().get_height()) / tileset.tile_size.y) + indices.resize(indices_x * indices_y) var indices := PackedInt32Array() var indices_x: int var indices_y: int @@ -10,9 +16,6 @@ var indices_y: int func _init(_tileset: TileSetCustom, _image: ImageExtended, _opacity := 1.0) -> void: super._init(_image, _opacity) tileset = _tileset - indices_x = ceili(float(get_image().get_width()) / tileset.tile_size.x) - indices_y = ceili(float(get_image().get_height()) / tileset.tile_size.y) - indices.resize(indices_x * indices_y) func set_index(tile_position: int, index: int) -> void: @@ -23,23 +26,6 @@ func set_index(tile_position: int, index: int) -> void: Global.canvas.queue_redraw() -func update_texture() -> void: - if TileSetPanel.tile_editing_mode == TileSetPanel.TileEditingMode.MANUAL: - for i in indices.size(): - var index := indices[i] - # Prevent from drawing on empty image portions. - if index == 0 and tileset.tiles.size() > 1: - var coords := get_tile_coords(i) - var current_tile := tileset.tiles[index] - var tile_size := current_tile.image.get_size() - image.blit_rect(current_tile.image, Rect2i(Vector2i.ZERO, tile_size), coords) - super.update_texture() - - -func on_undo_redo(undo: bool) -> void: - update_tileset(undo) - - func update_tileset(undo: bool) -> void: for i in indices.size(): var coords := get_tile_coords(i) @@ -238,5 +224,34 @@ func re_index_all_tiles() -> void: break +# Overridden Methods: +func update_texture() -> void: + if TileSetPanel.tile_editing_mode == TileSetPanel.TileEditingMode.MANUAL: + for i in indices.size(): + var index := indices[i] + # Prevent from drawing on empty image portions. + if index == 0 and tileset.tiles.size() > 1: + var coords := get_tile_coords(i) + var current_tile := tileset.tiles[index] + var tile_size := current_tile.image.get_size() + image.blit_rect(current_tile.image, Rect2i(Vector2i.ZERO, tile_size), coords) + super.update_texture() + + +func on_undo_redo(undo: bool) -> void: + update_tileset(undo) + + +func serialize() -> Dictionary: + var dict := super.serialize() + dict["tile_indices"] = indices + return dict + + +func deserialize(dict: Dictionary) -> void: + super.deserialize(dict) + indices = dict.get("tile_indices") + + func get_class_name() -> String: return "CelTileMap" diff --git a/src/Classes/Layers/LayerTileMap.gd b/src/Classes/Layers/LayerTileMap.gd index c6a2296cc..1f73737ee 100644 --- a/src/Classes/Layers/LayerTileMap.gd +++ b/src/Classes/Layers/LayerTileMap.gd @@ -12,6 +12,19 @@ func _init(_project: Project, _tileset: TileSetCustom, _name := "") -> void: # Overridden Methods: +func serialize() -> Dictionary: + var dict := super.serialize() + dict["tileset_index"] = project.tilesets.find(tileset) + return dict + + +func deserialize(dict: Dictionary) -> void: + super.deserialize(dict) + new_cels_linked = dict.new_cels_linked + var tileset_index = dict.get("tileset_index") + tileset = project.tilesets[tileset_index] + + func get_layer_type() -> int: return Global.LayerTypes.TILEMAP @@ -23,3 +36,9 @@ func new_empty_cel() -> BaseCel: project.size.x, project.size.y, false, format, is_indexed ) return CelTileMap.new(tileset, image) + + +func new_cel_from_image(image: Image) -> PixelCel: + var image_extended := ImageExtended.new() + image_extended.copy_from_custom(image, project.is_indexed()) + return CelTileMap.new(tileset, image_extended) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index bdea6a52b..512bf1310 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -296,6 +296,9 @@ func serialize() -> Dictionary: var reference_image_data := [] for reference_image in reference_images: reference_image_data.append(reference_image.serialize()) + var tileset_data := [] + for tileset in tilesets: + tileset_data.append(tileset.serialize()) var metadata := _serialize_metadata(self) @@ -316,6 +319,7 @@ func serialize() -> Dictionary: "frames": frame_data, "brushes": brush_data, "reference_images": reference_image_data, + "tilesets": tileset_data, "vanishing_points": vanishing_points, "export_file_name": file_name, "export_file_format": file_format, @@ -345,6 +349,12 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces if dict.has("tile_mode_y_basis_x") and dict.has("tile_mode_y_basis_y"): tiles.y_basis.x = dict.tile_mode_y_basis_x tiles.y_basis.y = dict.tile_mode_y_basis_y + 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) + tileset.deserialize(saved_tileset) + tilesets.append(tileset) if dict.has("frames") and dict.has("layers"): for saved_layer in dict.layers: match int(saved_layer.get("type", Global.LayerTypes.PIXEL)): @@ -354,63 +364,8 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces layers.append(GroupLayer.new(self)) Global.LayerTypes.THREE_D: layers.append(Layer3D.new(self)) - - var frame_i := 0 - for frame in dict.frames: - var cels: Array[BaseCel] = [] - var cel_i := 0 - for cel in frame.cels: - match int(dict.layers[cel_i].get("type", Global.LayerTypes.PIXEL)): - Global.LayerTypes.PIXEL: - var image: Image - var indices_data := PackedByteArray() - if is_instance_valid(zip_reader): # For pxo files saved in 1.0+ - var path := "image_data/frames/%s/layer_%s" % [frame_i + 1, cel_i + 1] - var image_data := zip_reader.read_file(path) - image = Image.create_from_data( - size.x, size.y, false, get_image_format(), image_data - ) - var indices_path := ( - "image_data/frames/%s/indices_layer_%s" % [frame_i + 1, cel_i + 1] - ) - if zip_reader.file_exists(indices_path): - indices_data = zip_reader.read_file(indices_path) - elif is_instance_valid(file): # For pxo files saved in 0.x - var buffer := file.get_buffer(size.x * size.y * 4) - image = Image.create_from_data( - size.x, size.y, false, get_image_format(), buffer - ) - var pixelorama_image := ImageExtended.new() - pixelorama_image.is_indexed = is_indexed() - if not indices_data.is_empty() and is_indexed(): - pixelorama_image.indices_image = Image.create_from_data( - size.x, size.y, false, Image.FORMAT_R8, indices_data - ) - pixelorama_image.copy_from(image) - pixelorama_image.select_palette("", true) - cels.append(PixelCel.new(pixelorama_image)) - Global.LayerTypes.GROUP: - cels.append(GroupCel.new()) - Global.LayerTypes.THREE_D: - if is_instance_valid(file): # For pxo files saved in 0.x - # Don't do anything with it, just read it so that the file can move on - file.get_buffer(size.x * size.y * 4) - cels.append(Cel3D.new(size, true)) - cel["pxo_version"] = pxo_version - cels[cel_i].deserialize(cel) - _deserialize_metadata(cels[cel_i], cel) - cel_i += 1 - var duration := 1.0 - if frame.has("duration"): - duration = frame.duration - elif dict.has("frame_duration"): - duration = dict.frame_duration[frame_i] - - var frame_class := Frame.new(cels, duration) - frame_class.user_data = frame.get("user_data", "") - _deserialize_metadata(frame_class, frame) - frames.append(frame_class) - frame_i += 1 + Global.LayerTypes.TILEMAP: + layers.append(LayerTileMap.new(self, null)) # Parent references to other layers are created when deserializing # a layer, so loop again after creating them: @@ -426,6 +381,43 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces layer_dict["blend_mode"] = blend_mode layers[layer_i].deserialize(layer_dict) _deserialize_metadata(layers[layer_i], dict.layers[layer_i]) + + var frame_i := 0 + for frame in dict.frames: + var cels: Array[BaseCel] = [] + var cel_i := 0 + for cel in frame.cels: + var layer := layers[cel_i] + match layer.get_layer_type(): + Global.LayerTypes.PIXEL: + var image := _load_image_from_pxo(frame_i, cel_i, zip_reader, file) + cels.append(PixelCel.new(image)) + Global.LayerTypes.GROUP: + cels.append(GroupCel.new()) + Global.LayerTypes.THREE_D: + if is_instance_valid(file): # For pxo files saved in 0.x + # Don't do anything with it, just read it so that the file can move on + file.get_buffer(size.x * size.y * 4) + cels.append(Cel3D.new(size, true)) + Global.LayerTypes.TILEMAP: + var image := _load_image_from_pxo(frame_i, cel_i, zip_reader, file) + var new_cel := (layer as LayerTileMap).new_cel_from_image(image) + cels.append(new_cel) + cel["pxo_version"] = pxo_version + cels[cel_i].deserialize(cel) + _deserialize_metadata(cels[cel_i], cel) + cel_i += 1 + var duration := 1.0 + if frame.has("duration"): + duration = frame.duration + elif dict.has("frame_duration"): + duration = dict.frame_duration[frame_i] + + var frame_class := Frame.new(cels, duration) + frame_class.user_data = frame.get("user_data", "") + _deserialize_metadata(frame_class, frame) + frames.append(frame_class) + frame_i += 1 if dict.has("tags"): for tag in dict.tags: var new_tag := AnimationTag.new(tag.name, Color(tag.color), tag.from, tag.to) @@ -484,6 +476,32 @@ func _deserialize_metadata(object: Object, dict: Dictionary) -> void: object.set_meta(meta, metadata[meta]) +func _load_image_from_pxo( + frame_i: int, cel_i: int, zip_reader: ZIPReader, file: FileAccess +) -> ImageExtended: + var image: Image + var indices_data := PackedByteArray() + if is_instance_valid(zip_reader): # For pxo files saved in 1.0+ + var path := "image_data/frames/%s/layer_%s" % [frame_i + 1, cel_i + 1] + var image_data := zip_reader.read_file(path) + image = Image.create_from_data(size.x, size.y, false, get_image_format(), image_data) + var indices_path := "image_data/frames/%s/indices_layer_%s" % [frame_i + 1, cel_i + 1] + if zip_reader.file_exists(indices_path): + indices_data = zip_reader.read_file(indices_path) + elif is_instance_valid(file): # For pxo files saved in 0.x + var buffer := file.get_buffer(size.x * size.y * 4) + image = Image.create_from_data(size.x, size.y, false, get_image_format(), buffer) + var pixelorama_image := ImageExtended.new() + pixelorama_image.is_indexed = is_indexed() + if not indices_data.is_empty() and is_indexed(): + pixelorama_image.indices_image = Image.create_from_data( + size.x, size.y, false, Image.FORMAT_R8, indices_data + ) + pixelorama_image.copy_from(image) + pixelorama_image.select_palette("", true) + return pixelorama_image + + func _size_changed(value: Vector2i) -> void: if not is_instance_valid(tiles): size = value diff --git a/src/Classes/TileSetCustom.gd b/src/Classes/TileSetCustom.gd index 8fb9b3849..84531817c 100644 --- a/src/Classes/TileSetCustom.gd +++ b/src/Classes/TileSetCustom.gd @@ -82,3 +82,12 @@ func remove_unused_tiles() -> bool: remove_tile_at_index(i) tile_removed = true return tile_removed + + +func serialize() -> Dictionary: + return {"name": name, "tile_size": tile_size, "tile_amount": tiles.size()} + + +func deserialize(dict: Dictionary) -> void: + name = dict.get("name", name) + tile_size = str_to_var("Vector2i" + dict.get("tile_size"))