diff --git a/Translations/Translations.pot b/Translations/Translations.pot index 6fdade8c6..3d7396c58 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -3551,3 +3551,11 @@ msgstr "" #: src/UI/TilesPanel.tscn msgid "Show empty tile:" msgstr "" + +#: src/UI/TilesPanel.tscn +msgid "Tile properties" +msgstr "" + +#: src/UI/TilesPanel.tscn +msgid "Probability:" +msgstr "" diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 97397a6e8..044d50245 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -287,7 +287,10 @@ 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, 0) + if j > tileset.tiles.size() - 1: + tileset.add_tile(image, null, 0) + else: + tileset.tiles[j].image = image for cel in new_project.get_all_pixel_cels(): if cel is CelTileMap: cel.find_times_used_of_tiles() diff --git a/src/Classes/TileSetCustom.gd b/src/Classes/TileSetCustom.gd index 39eb7dbcb..a59a3c79c 100644 --- a/src/Classes/TileSetCustom.gd +++ b/src/Classes/TileSetCustom.gd @@ -29,6 +29,8 @@ class Tile: var image: Image ## The amount of tiles this tile is being used in tilemaps. var times_used := 1 + ## The relative probability of this tile appearing when drawing random tiles. + var probability := 1.0 func _init(_image: Image) -> void: image = _image @@ -38,6 +40,14 @@ class Tile: func can_be_removed() -> bool: return times_used <= 0 + func serialize() -> Dictionary: + return {"times_used": times_used, "probability": probability} + + func deserialize(dict: Dictionary, skip_times_used := false) -> void: + if not skip_times_used: + times_used = dict.get("times_used", times_used) + probability = dict.get("probability", probability) + func _init(_tile_size: Vector2i, _name := "", add_empty_tile := true) -> void: tile_size = _tile_size @@ -150,10 +160,31 @@ func find_using_layers(project: Project) -> Array[LayerTileMap]: return tilemaps +func pick_random_tile(selected_tile_indices: Array[int]) -> int: + if selected_tile_indices.is_empty(): + for i in tiles.size(): + selected_tile_indices.append(i) + var sum := 0.0 + for i in selected_tile_indices: + sum += tiles[i].probability + var rand := randf_range(0.0, sum) + var current := 0.0 + for i in selected_tile_indices: + current += tiles[i].probability + if current >= rand: + return i + return selected_tile_indices[0] + + ## Serializes the data of this class into the form of a [Dictionary], ## which is used so the data can be stored in pxo files. func serialize() -> Dictionary: - return {"name": name, "tile_size": tile_size, "tile_amount": tiles.size()} + var dict := {"name": name, "tile_size": tile_size, "tile_amount": tiles.size()} + var tile_data := {} + for i in tiles.size(): + tile_data[i] = tiles[i].serialize() + dict["tile_data"] = tile_data + return dict ## Deserializes the data of a given [member dict] [Dictionary] into class data, @@ -161,6 +192,16 @@ func serialize() -> Dictionary: func deserialize(dict: Dictionary) -> void: name = dict.get("name", name) tile_size = str_to_var("Vector2i" + dict.get("tile_size")) + var tile_data := dict.get("tile_data", {}) as Dictionary + for i_str in tile_data: + var i := int(i_str) + var tile: Tile + if i > tiles.size() - 1: + tile = Tile.new(null) + tiles.append(tile) + else: + tile = tiles[i] + tile.deserialize(tile_data[i_str], true) ## Serializes the data of each tile in [member tiles] into the form of a [Dictionary], @@ -169,7 +210,7 @@ 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] + dict[tile.image] = [image_data.compress(), image_data.size(), tile.serialize()] return dict @@ -180,9 +221,10 @@ func deserialize_undo_data(dict: Dictionary, cel: CelTileMap) -> void: for image: Image in dict: var tile_data = dict[image] var buffer_size := tile_data[1] as int + var tile_dictionary := tile_data[2] as Dictionary 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] + tiles[i].deserialize(tile_dictionary) i += 1 updated.emit(cel, -1) diff --git a/src/UI/TilesPanel.gd b/src/UI/TilesPanel.gd index b85ef490e..dfed4dfc9 100644 --- a/src/UI/TilesPanel.gd +++ b/src/UI/TilesPanel.gd @@ -24,7 +24,9 @@ static var selected_tile_index := 0: selected_tiles = [value] _call_update_brushes() get: - return selected_tiles.pick_random() + if is_instance_valid(current_tileset): + return current_tileset.pick_random_tile(selected_tiles) + return selected_tiles[0] static var selected_tiles: Array[int] = [0] static var is_flipped_h := false: set(value): @@ -38,7 +40,7 @@ static var is_transposed := false: set(value): is_transposed = value _call_update_brushes() -var current_tileset: TileSetCustom +static var current_tileset: TileSetCustom var button_size := 36: set(value): if button_size == value: @@ -61,6 +63,8 @@ var tile_index_menu_popped := 0 @onready var options: Popup = $Options @onready var tile_size_slider: ValueSlider = %TileSizeSlider @onready var tile_button_popup_menu: PopupMenu = $TileButtonPopupMenu +@onready var tile_properties: AcceptDialog = $TileProperties +@onready var tile_probability_slider: ValueSlider = %TileProbabilitySlider func _ready() -> void: @@ -197,7 +201,7 @@ func _on_tile_button_gui_input(event: InputEvent, index: int) -> void: tile_button_popup_menu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2.ONE)) tile_index_menu_popped = index tile_button_popup_menu.set_item_disabled( - 0, not current_tileset.tiles[index].can_be_removed() + 1, not current_tileset.tiles[index].can_be_removed() ) @@ -277,9 +281,12 @@ func _on_show_empty_tile_toggled(toggled_on: bool) -> void: func _on_tile_button_popup_menu_index_pressed(index: int) -> void: - if tile_index_menu_popped == 0: - return - if index == 0: # Delete + if index == 0: # Properties + tile_probability_slider.value = current_tileset.tiles[tile_index_menu_popped].probability + tile_properties.popup_centered() + elif index == 1: # Delete + if tile_index_menu_popped == 0: + return if current_tileset.tiles[tile_index_menu_popped].can_be_removed(): var undo_data := current_tileset.serialize_undo_data() current_tileset.tiles.remove_at(tile_index_menu_popped) @@ -295,3 +302,7 @@ func _on_tile_button_popup_menu_index_pressed(index: int) -> void: project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true)) project.undo_redo.add_do_method(Global.undo_or_redo.bind(false)) project.undo_redo.commit_action() + + +func _on_tile_probability_slider_value_changed(value: float) -> void: + current_tileset.tiles[tile_index_menu_popped].probability = value diff --git a/src/UI/TilesPanel.tscn b/src/UI/TilesPanel.tscn index e77ac7312..1ea9b8f40 100644 --- a/src/UI/TilesPanel.tscn +++ b/src/UI/TilesPanel.tscn @@ -343,8 +343,49 @@ button_pressed = true text = "On" [node name="TileButtonPopupMenu" type="PopupMenu" parent="."] -item_count = 1 -item_0/text = "Delete" +item_count = 2 +item_0/text = "Properties" +item_1/text = "Delete" +item_1/id = 1 + +[node name="TileProperties" type="AcceptDialog" parent="."] +title = "Tile properties" +size = Vector2i(300, 200) + +[node name="GridContainer" type="GridContainer" parent="TileProperties"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = -8.0 +offset_bottom = -49.0 +grow_horizontal = 2 +grow_vertical = 2 +columns = 2 + +[node name="TileProbabilityLabel" type="Label" parent="TileProperties/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Probability:" + +[node name="TileProbabilitySlider" type="TextureProgressBar" parent="TileProperties/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +focus_mode = 2 +mouse_default_cursor_shape = 2 +theme_type_variation = &"ValueSlider" +max_value = 10.0 +step = 0.001 +value = 1.0 +allow_greater = true +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +script = ExtResource("10_wfr6s") [connection signal="toggled" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/PlaceTiles" to="." method="_on_place_tiles_toggled"] [connection signal="pressed" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/RotateLeftButton" to="." method="_on_rotate_pressed" binds= [false]] @@ -358,3 +399,4 @@ item_0/text = "Delete" [connection signal="value_changed" from="Options/MarginContainer/ScrollContainer/GridContainer/TileSizeSlider" to="." method="_on_tile_size_slider_value_changed"] [connection signal="toggled" from="Options/MarginContainer/ScrollContainer/GridContainer/ShowEmptyTile" to="." method="_on_show_empty_tile_toggled"] [connection signal="index_pressed" from="TileButtonPopupMenu" to="." method="_on_tile_button_popup_menu_index_pressed"] +[connection signal="value_changed" from="TileProperties/GridContainer/TileProbabilitySlider" to="." method="_on_tile_probability_slider_value_changed"]