mirror of
https://github.com/JHDev2006/Super-Mario-Bros.-Remastered-Public.git
synced 2025-10-22 07:28:14 +00:00
940 lines
33 KiB
GDScript
Executable File
940 lines
33 KiB
GDScript
Executable File
@tool
|
|
extends Control
|
|
|
|
signal update_overlay
|
|
signal force_show_terrains
|
|
|
|
# The maximum individual tiles the overlay will draw before shortcutting the display
|
|
# To prevent editor lag when drawing large rectangles or filling large areas
|
|
const MAX_CANVAS_RENDER_TILES = 1500
|
|
const TERRAIN_PROPERTIES_SCENE := preload("res://addons/better-terrain/editor/TerrainProperties.tscn")
|
|
const TERRAIN_ENTRY_SCENE := preload("res://addons/better-terrain/editor/TerrainEntry.tscn")
|
|
const MIN_ZOOM_SETTING := "editor/better_terrain/min_zoom_amount"
|
|
const MAX_ZOOM_SETTING := "editor/better_terrain/max_zoom_amount"
|
|
|
|
|
|
# Buttons
|
|
@onready var draw_button: Button = $VBox/Toolbar/Draw
|
|
@onready var line_button: Button = $VBox/Toolbar/Line
|
|
@onready var rectangle_button: Button = $VBox/Toolbar/Rectangle
|
|
@onready var fill_button: Button = $VBox/Toolbar/Fill
|
|
@onready var replace_button: Button = $VBox/Toolbar/Replace
|
|
|
|
@onready var paint_type: Button = $VBox/Toolbar/PaintType
|
|
@onready var paint_terrain: Button = $VBox/Toolbar/PaintTerrain
|
|
@onready var select_tiles: Button = $VBox/Toolbar/SelectTiles
|
|
|
|
@onready var paint_symmetry: Button = $VBox/Toolbar/PaintSymmetry
|
|
@onready var symmetry_options: OptionButton = $VBox/Toolbar/SymmetryOptions
|
|
|
|
@onready var shuffle_random: Button = $VBox/Toolbar/ShuffleRandom
|
|
@onready var zoom_slider_container: VBoxContainer = $VBox/Toolbar/ZoomContainer
|
|
|
|
@onready var source_selector: MenuBar = $VBox/Toolbar/Sources
|
|
@onready var source_selector_popup: PopupMenu = $VBox/Toolbar/Sources/Sources
|
|
|
|
@onready var clean_button: Button = $VBox/Toolbar/Clean
|
|
@onready var layer_up: Button = $VBox/Toolbar/LayerUp
|
|
@onready var layer_down: Button = $VBox/Toolbar/LayerDown
|
|
@onready var layer_highlight: Button = $VBox/Toolbar/LayerHighlight
|
|
@onready var layer_grid: Button = $VBox/Toolbar/LayerGrid
|
|
|
|
@onready var grid_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/GridMode
|
|
@onready var quick_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/QuickMode
|
|
|
|
@onready var edit_tool_buttons: HBoxContainer = $VBox/HSplit/Terrains/LowerToolbar/EditTools
|
|
@onready var add_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain
|
|
@onready var edit_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain
|
|
@onready var pick_icon_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon
|
|
@onready var move_up_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp
|
|
@onready var move_down_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown
|
|
@onready var remove_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain
|
|
|
|
@onready var scroll_container: ScrollContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer
|
|
@onready var terrain_list: HFlowContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer/TerrainList
|
|
@onready var tile_view: Control = $VBox/HSplit/Panel/ScrollArea/TileView
|
|
|
|
|
|
var selected_entry := -2
|
|
|
|
var tilemap : TileMapLayer
|
|
var tileset : TileSet
|
|
|
|
var undo_manager : EditorUndoRedoManager
|
|
var terrain_undo
|
|
|
|
var draw_overlay := false
|
|
var initial_click : Vector2i
|
|
var prev_position : Vector2i
|
|
var current_position : Vector2i
|
|
var tileset_dirty := false
|
|
var zoom_slider : HSlider
|
|
|
|
enum PaintMode {
|
|
NO_PAINT,
|
|
PAINT,
|
|
ERASE
|
|
}
|
|
|
|
enum PaintAction {
|
|
NO_ACTION,
|
|
LINE,
|
|
RECT
|
|
}
|
|
|
|
enum SourceSelectors {
|
|
ALL = 1000000,
|
|
NONE = 1000001,
|
|
}
|
|
|
|
var paint_mode := PaintMode.NO_PAINT
|
|
|
|
var paint_action := PaintAction.NO_ACTION
|
|
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready() -> void:
|
|
draw_button.icon = get_theme_icon("Edit", "EditorIcons")
|
|
line_button.icon = get_theme_icon("Line", "EditorIcons")
|
|
rectangle_button.icon = get_theme_icon("Rectangle", "EditorIcons")
|
|
fill_button.icon = get_theme_icon("Bucket", "EditorIcons")
|
|
select_tiles.icon = get_theme_icon("ToolSelect", "EditorIcons")
|
|
add_terrain_button.icon = get_theme_icon("Add", "EditorIcons")
|
|
edit_terrain_button.icon = get_theme_icon("Tools", "EditorIcons")
|
|
pick_icon_button.icon = get_theme_icon("ColorPick", "EditorIcons")
|
|
move_up_button.icon = get_theme_icon("ArrowUp", "EditorIcons")
|
|
move_down_button.icon = get_theme_icon("ArrowDown", "EditorIcons")
|
|
remove_terrain_button.icon = get_theme_icon("Remove", "EditorIcons")
|
|
grid_mode_button.icon = get_theme_icon("FileThumbnail", "EditorIcons")
|
|
quick_mode_button.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
|
|
layer_up.icon = get_theme_icon("MoveUp", "EditorIcons")
|
|
layer_down.icon = get_theme_icon("MoveDown", "EditorIcons")
|
|
layer_highlight.icon = get_theme_icon("TileMapHighlightSelected", "EditorIcons")
|
|
layer_grid.icon = get_theme_icon("Grid", "EditorIcons")
|
|
|
|
select_tiles.button_group.pressed.connect(_on_bit_button_pressed)
|
|
|
|
terrain_undo = load("res://addons/better-terrain/editor/TerrainUndo.gd").new()
|
|
add_child(terrain_undo)
|
|
tile_view.undo_manager = undo_manager
|
|
tile_view.terrain_undo = terrain_undo
|
|
|
|
tile_view.paste_occurred.connect(_on_paste_occurred)
|
|
tile_view.change_zoom_level.connect(_on_change_zoom_level)
|
|
tile_view.terrain_updated.connect(_on_terrain_updated)
|
|
|
|
# Zoom slider is manipulated by settings, make it at runtime
|
|
zoom_slider = HSlider.new()
|
|
zoom_slider.custom_minimum_size = Vector2(100, 0)
|
|
zoom_slider.value_changed.connect(tile_view._on_zoom_value_changed)
|
|
zoom_slider_container.add_child(zoom_slider)
|
|
|
|
# Init settings if needed
|
|
if !ProjectSettings.has_setting(MIN_ZOOM_SETTING):
|
|
ProjectSettings.set(MIN_ZOOM_SETTING, 1.0)
|
|
ProjectSettings.add_property_info({
|
|
"name": MIN_ZOOM_SETTING,
|
|
"type": TYPE_FLOAT,
|
|
"hint": PROPERTY_HINT_RANGE,
|
|
"hint_string": "0.1,1.0,0.1"
|
|
})
|
|
ProjectSettings.set_initial_value(MIN_ZOOM_SETTING, 1.0)
|
|
ProjectSettings.set_as_basic(MIN_ZOOM_SETTING, true)
|
|
|
|
if !ProjectSettings.has_setting(MAX_ZOOM_SETTING):
|
|
ProjectSettings.set(MAX_ZOOM_SETTING, 8.0)
|
|
ProjectSettings.add_property_info({
|
|
"name": MAX_ZOOM_SETTING,
|
|
"type": TYPE_FLOAT,
|
|
"hint": PROPERTY_HINT_RANGE,
|
|
"hint_string": "2.0,32.0,1.0"
|
|
})
|
|
ProjectSettings.set_initial_value(MAX_ZOOM_SETTING, 8.0)
|
|
ProjectSettings.set_as_basic(MAX_ZOOM_SETTING, true)
|
|
ProjectSettings.set_order(MAX_ZOOM_SETTING, ProjectSettings.get_order(MIN_ZOOM_SETTING) + 1)
|
|
|
|
ProjectSettings.settings_changed.connect(_on_adjust_settings)
|
|
_on_adjust_settings()
|
|
zoom_slider.value = 1.0
|
|
|
|
|
|
func _process(delta):
|
|
scroll_container.scroll_horizontal = 0
|
|
|
|
|
|
func _on_adjust_settings():
|
|
zoom_slider.min_value = ProjectSettings.get_setting(MIN_ZOOM_SETTING, 1.0)
|
|
zoom_slider.max_value = ProjectSettings.get_setting(MAX_ZOOM_SETTING, 8.0)
|
|
zoom_slider.step = (zoom_slider.max_value - zoom_slider.min_value) / 100.0
|
|
|
|
|
|
func _get_fill_cells(target: Vector2i) -> Array:
|
|
var pick := BetterTerrain.get_cell(tilemap, target)
|
|
var bounds := tilemap.get_used_rect()
|
|
var neighbors = BetterTerrain.data.cells_adjacent_for_fill(tileset)
|
|
|
|
# No sets yet, so use a dictionary
|
|
var checked := {}
|
|
var pending := [target]
|
|
var goal := []
|
|
|
|
while !pending.is_empty():
|
|
var p = pending.pop_front()
|
|
if checked.has(p):
|
|
continue
|
|
checked[p] = true
|
|
if !bounds.has_point(p) or BetterTerrain.get_cell(tilemap, p) != pick:
|
|
continue
|
|
|
|
goal.append(p)
|
|
pending.append_array(BetterTerrain.data.neighboring_coords(tilemap, p, neighbors))
|
|
|
|
return goal
|
|
|
|
|
|
func tiles_about_to_change() -> void:
|
|
if tileset and tileset.changed.is_connected(queue_tiles_changed):
|
|
tileset.changed.disconnect(queue_tiles_changed)
|
|
|
|
|
|
func tiles_changed() -> void:
|
|
# ensure up to date
|
|
BetterTerrain._update_terrain_data(tileset)
|
|
|
|
# clear terrains
|
|
for c in terrain_list.get_children():
|
|
terrain_list.remove_child(c)
|
|
c.queue_free()
|
|
|
|
# load terrains from tileset
|
|
var terrain_count := BetterTerrain.terrain_count(tileset)
|
|
var item_count = terrain_count + 1
|
|
for i in terrain_count:
|
|
var terrain := BetterTerrain.get_terrain(tileset, i)
|
|
if i >= terrain_list.get_child_count():
|
|
add_terrain_entry(terrain, i)
|
|
|
|
if item_count > terrain_list.get_child_count():
|
|
var terrain := BetterTerrain.get_terrain(tileset, BetterTerrain.TileCategory.EMPTY)
|
|
if terrain.valid:
|
|
add_terrain_entry(terrain, item_count - 1)
|
|
|
|
while item_count < terrain_list.get_child_count():
|
|
var child = terrain_list.get_child(terrain_list.get_child_count() - 1)
|
|
terrain_list.remove_child(child)
|
|
child.free()
|
|
|
|
source_selector_popup.clear()
|
|
source_selector_popup.add_item("All", SourceSelectors.ALL)
|
|
source_selector_popup.add_item("None", SourceSelectors.NONE)
|
|
var source_count = tileset.get_source_count() if tileset else 0
|
|
for s in source_count:
|
|
var source_id = tileset.get_source_id(s)
|
|
var source := tileset.get_source(source_id)
|
|
if !(source is TileSetAtlasSource):
|
|
continue
|
|
|
|
var name := source.resource_name
|
|
if name.is_empty():
|
|
var texture := (source as TileSetAtlasSource).texture
|
|
var texture_name := texture.resource_name if texture else ""
|
|
if !texture_name.is_empty():
|
|
name = texture_name
|
|
else:
|
|
var texture_path := texture.resource_path if texture else ""
|
|
if !texture_path.is_empty():
|
|
name = texture_path.get_file()
|
|
|
|
if !name.is_empty():
|
|
name += " "
|
|
name += " (ID: %d)" % source_id
|
|
|
|
source_selector_popup.add_check_item(name, source_id)
|
|
source_selector_popup.set_item_checked(source_selector_popup.get_item_index(source_id), true)
|
|
source_selector.visible = source_selector_popup.item_count > 3 # All, None and more than one source
|
|
|
|
update_tile_view_paint()
|
|
tile_view.refresh_tileset(tileset)
|
|
|
|
if tileset and !tileset.changed.is_connected(queue_tiles_changed):
|
|
tileset.changed.connect(queue_tiles_changed)
|
|
|
|
clean_button.visible = BetterTerrain._has_invalid_peering_types(tileset)
|
|
|
|
tileset_dirty = false
|
|
_on_grid_mode_pressed()
|
|
_on_quick_mode_pressed()
|
|
|
|
|
|
func about_to_be_visible(visible: bool) -> void:
|
|
if !visible:
|
|
return
|
|
|
|
if tileset != tilemap.tile_set:
|
|
tiles_about_to_change()
|
|
tileset = tilemap.tile_set
|
|
tiles_changed()
|
|
|
|
var settings := EditorInterface.get_editor_settings()
|
|
layer_highlight.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/highlight_selected_layer"))
|
|
layer_grid.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/display_grid"))
|
|
|
|
|
|
func queue_tiles_changed() -> void:
|
|
# Bring terrain data up to date with complex tileset changes
|
|
if !tileset or tileset_dirty:
|
|
return
|
|
|
|
tileset_dirty = true
|
|
tiles_changed.call_deferred()
|
|
|
|
|
|
func _on_entry_select(index:int):
|
|
selected_entry = index
|
|
if selected_entry >= BetterTerrain.terrain_count(tileset):
|
|
selected_entry = BetterTerrain.TileCategory.EMPTY
|
|
for i in range(terrain_list.get_child_count()):
|
|
if i != index:
|
|
terrain_list.get_child(i).set_selected(false)
|
|
update_tile_view_paint()
|
|
|
|
|
|
func _on_clean_pressed() -> void:
|
|
var confirmed := [false]
|
|
var popup := ConfirmationDialog.new()
|
|
popup.dialog_text = tr("Tile set changes have caused terrain to become invalid. Remove invalid terrain data?")
|
|
popup.dialog_hide_on_ok = false
|
|
popup.confirmed.connect(func():
|
|
confirmed[0] = true
|
|
popup.hide()
|
|
)
|
|
EditorInterface.popup_dialog_centered(popup)
|
|
await popup.visibility_changed
|
|
popup.queue_free()
|
|
|
|
if confirmed[0]:
|
|
undo_manager.create_action("Clean invalid terrain peering data", UndoRedo.MERGE_DISABLE, tileset)
|
|
undo_manager.add_do_method(BetterTerrain, &"_clear_invalid_peering_types", tileset)
|
|
undo_manager.add_do_method(self, &"tiles_changed")
|
|
terrain_undo.create_peering_restore_point(undo_manager, tileset)
|
|
undo_manager.add_undo_method(self, &"tiles_changed")
|
|
undo_manager.commit_action()
|
|
|
|
|
|
func _on_grid_mode_pressed() -> void:
|
|
for c in terrain_list.get_children():
|
|
c.grid_mode = grid_mode_button.button_pressed
|
|
c.update_style()
|
|
|
|
|
|
func _on_quick_mode_pressed() -> void:
|
|
edit_tool_buttons.visible = !quick_mode_button.button_pressed
|
|
for c in terrain_list.get_children():
|
|
c.visible = !quick_mode_button.button_pressed or c.terrain.type in [BetterTerrain.TerrainType.MATCH_TILES, BetterTerrain.TerrainType.MATCH_VERTICES]
|
|
|
|
|
|
func update_tile_view_paint() -> void:
|
|
tile_view.paint = selected_entry
|
|
tile_view.queue_redraw()
|
|
|
|
var editable = tile_view.paint != BetterTerrain.TileCategory.EMPTY
|
|
edit_terrain_button.disabled = !editable
|
|
move_up_button.disabled = !editable or tile_view.paint == 0
|
|
move_down_button.disabled = !editable or tile_view.paint == BetterTerrain.terrain_count(tileset) - 1
|
|
remove_terrain_button.disabled = !editable
|
|
pick_icon_button.disabled = !editable
|
|
|
|
|
|
func _on_add_terrain_pressed() -> void:
|
|
if !tileset:
|
|
return
|
|
|
|
var popup := TERRAIN_PROPERTIES_SCENE.instantiate()
|
|
popup.set_category_data(BetterTerrain.get_terrain_categories(tileset))
|
|
popup.terrain_name = "New terrain"
|
|
popup.terrain_color = Color.from_hsv(randf(), 0.3 + 0.7 * randf(), 0.6 + 0.4 * randf())
|
|
popup.terrain_icon = ""
|
|
popup.terrain_type = 0
|
|
EditorInterface.popup_dialog_centered(popup)
|
|
await popup.visibility_changed
|
|
if popup.accepted:
|
|
undo_manager.create_action("Add terrain type", UndoRedo.MERGE_DISABLE, tileset)
|
|
undo_manager.add_do_method(self, &"perform_add_terrain", popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon})
|
|
undo_manager.add_undo_method(self, &"perform_remove_terrain", terrain_list.get_child_count() - 1)
|
|
undo_manager.commit_action()
|
|
popup.queue_free()
|
|
|
|
|
|
func _on_edit_terrain_pressed() -> void:
|
|
if !tileset:
|
|
return
|
|
|
|
if selected_entry < 0:
|
|
return
|
|
|
|
var t := BetterTerrain.get_terrain(tileset, selected_entry)
|
|
var categories = BetterTerrain.get_terrain_categories(tileset)
|
|
categories = categories.filter(func(x): return x.id != selected_entry)
|
|
|
|
var popup := TERRAIN_PROPERTIES_SCENE.instantiate()
|
|
popup.set_category_data(categories)
|
|
|
|
t.icon = t.icon.duplicate()
|
|
|
|
popup.terrain_name = t.name
|
|
popup.terrain_type = t.type
|
|
popup.terrain_color = t.color
|
|
if t.has("icon") and t.icon.has("path"):
|
|
popup.terrain_icon = t.icon.path
|
|
popup.terrain_categories = t.categories
|
|
EditorInterface.popup_dialog_centered(popup)
|
|
await popup.visibility_changed
|
|
if popup.accepted:
|
|
undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset)
|
|
undo_manager.add_do_method(self, &"perform_edit_terrain", selected_entry, popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon})
|
|
undo_manager.add_undo_method(self, &"perform_edit_terrain", selected_entry, t.name, t.color, t.type, t.categories, t.icon)
|
|
if t.type != popup.terrain_type:
|
|
terrain_undo.create_terrain_type_restore_point(undo_manager, tileset)
|
|
terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry)
|
|
undo_manager.commit_action()
|
|
popup.queue_free()
|
|
|
|
|
|
func _on_pick_icon_pressed():
|
|
if selected_entry < 0:
|
|
return
|
|
tile_view.pick_icon_terrain = selected_entry
|
|
|
|
|
|
func _on_pick_icon_focus_exited():
|
|
tile_view.pick_icon_terrain_cancel = true
|
|
pick_icon_button.button_pressed = false
|
|
|
|
|
|
func _on_move_pressed(down: bool) -> void:
|
|
if !tileset:
|
|
return
|
|
|
|
if selected_entry < 0:
|
|
return
|
|
|
|
var index1 = selected_entry
|
|
var index2 = index1 + (1 if down else -1)
|
|
if index2 < 0 or index2 >= terrain_list.get_child_count():
|
|
return
|
|
|
|
undo_manager.create_action("Reorder terrains", UndoRedo.MERGE_DISABLE, tileset)
|
|
undo_manager.add_do_method(self, &"perform_swap_terrain", index1, index2)
|
|
undo_manager.add_undo_method(self, &"perform_swap_terrain", index1, index2)
|
|
undo_manager.commit_action()
|
|
|
|
|
|
func _on_remove_terrain_pressed() -> void:
|
|
if !tileset:
|
|
return
|
|
|
|
if selected_entry < 0:
|
|
return
|
|
|
|
# store confirmation in array to pass by ref
|
|
var t := BetterTerrain.get_terrain(tileset, selected_entry)
|
|
var confirmed := [false]
|
|
var popup := ConfirmationDialog.new()
|
|
popup.dialog_text = tr("Are you sure you want to remove {0}?").format([t.name])
|
|
popup.dialog_hide_on_ok = false
|
|
popup.confirmed.connect(func():
|
|
confirmed[0] = true
|
|
popup.hide()
|
|
)
|
|
EditorInterface.popup_dialog_centered(popup)
|
|
await popup.visibility_changed
|
|
popup.queue_free()
|
|
|
|
if confirmed[0]:
|
|
undo_manager.create_action("Remove terrain type", UndoRedo.MERGE_DISABLE, tileset)
|
|
undo_manager.add_do_method(self, &"perform_remove_terrain", selected_entry)
|
|
undo_manager.add_undo_method(self, &"perform_add_terrain", t.name, t.color, t.type, t.categories, t.icon)
|
|
for n in range(terrain_list.get_child_count() - 2, selected_entry, -1):
|
|
undo_manager.add_undo_method(self, &"perform_swap_terrain", n, n - 1)
|
|
if t.type == BetterTerrain.TerrainType.CATEGORY:
|
|
terrain_undo.create_terrain_type_restore_point(undo_manager, tileset)
|
|
terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry)
|
|
undo_manager.commit_action()
|
|
|
|
|
|
func add_terrain_entry(terrain:Dictionary, index:int = -1):
|
|
if index < 0:
|
|
index = terrain_list.get_child_count()
|
|
|
|
var entry = TERRAIN_ENTRY_SCENE.instantiate()
|
|
entry.tileset = tileset
|
|
entry.terrain = terrain
|
|
entry.grid_mode = grid_mode_button.button_pressed
|
|
entry.select.connect(_on_entry_select)
|
|
|
|
terrain_list.add_child(entry)
|
|
terrain_list.move_child(entry, index)
|
|
|
|
|
|
func remove_terrain_entry(index: int):
|
|
terrain_list.get_child(index).free()
|
|
for i in range(index, terrain_list.get_child_count()):
|
|
var child = terrain_list.get_child(i)
|
|
child.terrain = BetterTerrain.get_terrain(tileset, i)
|
|
child.update()
|
|
|
|
|
|
func perform_add_terrain(name: String, color: Color, type: int, categories: Array, icon:Dictionary = {}) -> void:
|
|
if BetterTerrain.add_terrain(tileset, name, color, type, categories, icon):
|
|
var index = BetterTerrain.terrain_count(tileset) - 1
|
|
var terrain = BetterTerrain.get_terrain(tileset, index)
|
|
add_terrain_entry(terrain, index)
|
|
|
|
|
|
func perform_remove_terrain(index: int) -> void:
|
|
if index >= BetterTerrain.terrain_count(tileset):
|
|
return
|
|
if BetterTerrain.remove_terrain(tileset, index):
|
|
remove_terrain_entry(index)
|
|
update_tile_view_paint()
|
|
|
|
|
|
func perform_swap_terrain(index1: int, index2: int) -> void:
|
|
var lower := min(index1, index2)
|
|
var higher := max(index1, index2)
|
|
if lower >= terrain_list.get_child_count() or higher >= terrain_list.get_child_count():
|
|
return
|
|
var item1 = terrain_list.get_child(lower)
|
|
var item2 = terrain_list.get_child(higher)
|
|
if BetterTerrain.swap_terrains(tileset, lower, higher):
|
|
terrain_list.move_child(item1, higher)
|
|
item1.terrain = BetterTerrain.get_terrain(tileset, higher)
|
|
item1.update()
|
|
item2.terrain = BetterTerrain.get_terrain(tileset, lower)
|
|
item2.update()
|
|
selected_entry = index2
|
|
terrain_list.get_child(index2).set_selected(true)
|
|
update_tile_view_paint()
|
|
|
|
|
|
func perform_edit_terrain(index: int, name: String, color: Color, type: int, categories: Array, icon: Dictionary = {}) -> void:
|
|
if index >= terrain_list.get_child_count():
|
|
return
|
|
var entry = terrain_list.get_child(index)
|
|
# don't overwrite empty icon
|
|
var valid_icon = icon
|
|
if icon.has("path") and icon.path.is_empty():
|
|
var terrain = BetterTerrain.get_terrain(tileset, index)
|
|
valid_icon = terrain.icon
|
|
if BetterTerrain.set_terrain(tileset, index, name, color, type, categories, valid_icon):
|
|
entry.terrain = BetterTerrain.get_terrain(tileset, index)
|
|
entry.update()
|
|
tile_view.queue_redraw()
|
|
|
|
|
|
func _on_shuffle_random_pressed():
|
|
BetterTerrain.use_seed = !shuffle_random.button_pressed
|
|
|
|
|
|
func _on_bit_button_pressed(button: BaseButton) -> void:
|
|
match select_tiles.button_group.get_pressed_button():
|
|
select_tiles: tile_view.paint_mode = tile_view.PaintMode.SELECT
|
|
paint_type: tile_view.paint_mode = tile_view.PaintMode.PAINT_TYPE
|
|
paint_terrain: tile_view.paint_mode = tile_view.PaintMode.PAINT_PEERING
|
|
paint_symmetry: tile_view.paint_mode = tile_view.PaintMode.PAINT_SYMMETRY
|
|
_: tile_view.paint_mode = tile_view.PaintMode.NO_PAINT
|
|
tile_view.queue_redraw()
|
|
|
|
symmetry_options.visible = paint_symmetry.button_pressed
|
|
|
|
|
|
func _on_symmetry_selected(index):
|
|
tile_view.paint_symmetry = index
|
|
|
|
|
|
func _on_paste_occurred():
|
|
select_tiles.button_pressed = true
|
|
|
|
|
|
func _on_change_zoom_level(value):
|
|
zoom_slider.value = value
|
|
|
|
|
|
func _on_terrain_updated(index):
|
|
var entry = terrain_list.get_child(index)
|
|
entry.terrain = BetterTerrain.get_terrain(tileset, index)
|
|
entry.update()
|
|
|
|
|
|
func canvas_tilemap_transform() -> Transform2D:
|
|
var transform := tilemap.get_viewport_transform() * tilemap.global_transform
|
|
|
|
# Handle subviewport
|
|
var editor_viewport := EditorInterface.get_editor_viewport_2d()
|
|
if tilemap.get_viewport() != editor_viewport:
|
|
var container = tilemap.get_viewport().get_parent() as SubViewportContainer
|
|
if container:
|
|
transform = editor_viewport.global_canvas_transform * container.get_transform() * transform
|
|
|
|
return transform
|
|
|
|
|
|
func canvas_draw(overlay: Control) -> void:
|
|
if !draw_overlay:
|
|
return
|
|
|
|
if selected_entry < 0:
|
|
return
|
|
|
|
var type = selected_entry
|
|
var terrain := BetterTerrain.get_terrain(tileset, type)
|
|
if !terrain.valid:
|
|
return
|
|
|
|
var tiles := []
|
|
var transform := canvas_tilemap_transform()
|
|
|
|
if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT:
|
|
var area := Rect2i(initial_click, current_position - initial_click).abs()
|
|
|
|
# Shortcut fill for large areas
|
|
if area.size.x > 1 and area.size.y > 1 and area.size.x * area.size.y > MAX_CANVAS_RENDER_TILES:
|
|
var shortcut := PackedVector2Array([
|
|
tilemap.map_to_local(area.position),
|
|
tilemap.map_to_local(Vector2i(area.end.x, area.position.y)),
|
|
tilemap.map_to_local(area.end),
|
|
tilemap.map_to_local(Vector2i(area.position.x, area.end.y))
|
|
])
|
|
overlay.draw_colored_polygon(transform * shortcut, Color(terrain.color, 0.5))
|
|
return
|
|
|
|
for y in range(area.position.y, area.end.y + 1):
|
|
for x in range(area.position.x, area.end.x + 1):
|
|
tiles.append(Vector2i(x, y))
|
|
elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT:
|
|
var cells := _get_tileset_line(initial_click, current_position, tileset)
|
|
var shape = BetterTerrain.data.cell_polygon(tileset)
|
|
for c in cells:
|
|
var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(c))
|
|
overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5))
|
|
elif fill_button.button_pressed:
|
|
tiles = _get_fill_cells(current_position)
|
|
if tiles.size() > MAX_CANVAS_RENDER_TILES:
|
|
tiles.resize(MAX_CANVAS_RENDER_TILES)
|
|
else:
|
|
tiles.append(current_position)
|
|
|
|
var shape = BetterTerrain.data.cell_polygon(tileset)
|
|
for t in tiles:
|
|
var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(t))
|
|
overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5))
|
|
|
|
|
|
func canvas_input(event: InputEvent) -> bool:
|
|
if selected_entry < 0:
|
|
return false
|
|
|
|
draw_overlay = true
|
|
if event is InputEventMouseMotion:
|
|
var tr := canvas_tilemap_transform()
|
|
var pos := tr.affine_inverse() * Vector2(event.position)
|
|
var event_position := tilemap.local_to_map(pos)
|
|
prev_position = current_position
|
|
if event_position == current_position:
|
|
return false
|
|
current_position = event_position
|
|
update_overlay.emit()
|
|
|
|
var replace_mode = replace_button.button_pressed
|
|
|
|
var released : bool = event is InputEventMouseButton and !event.pressed
|
|
if released:
|
|
terrain_undo.finish_action()
|
|
var type = selected_entry
|
|
if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT:
|
|
var area := Rect2i(initial_click, current_position - initial_click).abs()
|
|
# Fill from initial_target to target
|
|
undo_manager.create_action(tr("Draw terrain rectangle"), UndoRedo.MERGE_DISABLE, tilemap)
|
|
for y in range(area.position.y, area.end.y + 1):
|
|
for x in range(area.position.x, area.end.x + 1):
|
|
var coord := Vector2i(x, y)
|
|
if paint_mode == PaintMode.PAINT:
|
|
if replace_mode:
|
|
undo_manager.add_do_method(BetterTerrain, &"replace_cell", tilemap, coord, type)
|
|
else:
|
|
undo_manager.add_do_method(BetterTerrain, &"set_cell", tilemap, coord, type)
|
|
else:
|
|
undo_manager.add_do_method(tilemap, &"erase_cell", coord)
|
|
|
|
undo_manager.add_do_method(BetterTerrain, &"update_terrain_area", tilemap, area)
|
|
terrain_undo.create_tile_restore_point_area(undo_manager, tilemap, area)
|
|
undo_manager.commit_action()
|
|
update_overlay.emit()
|
|
elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT:
|
|
undo_manager.create_action(tr("Draw terrain line"), UndoRedo.MERGE_DISABLE, tilemap)
|
|
var cells := _get_tileset_line(initial_click, current_position, tileset)
|
|
if paint_mode == PaintMode.PAINT:
|
|
if replace_mode:
|
|
undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type)
|
|
else:
|
|
undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type)
|
|
elif paint_mode == PaintMode.ERASE:
|
|
for c in cells:
|
|
undo_manager.add_do_method(tilemap, &"erase_cell", c)
|
|
undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells)
|
|
terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
|
|
undo_manager.commit_action()
|
|
update_overlay.emit()
|
|
|
|
paint_mode = PaintMode.NO_PAINT
|
|
return true
|
|
|
|
var clicked : bool = event is InputEventMouseButton and event.pressed
|
|
if clicked:
|
|
paint_mode = PaintMode.NO_PAINT
|
|
|
|
if (event.is_command_or_control_pressed() and !event.shift_pressed):
|
|
var pick = BetterTerrain.get_cell(tilemap, current_position)
|
|
if pick >= 0:
|
|
terrain_list.get_children()[pick]._on_focus_entered()
|
|
#_on_entry_select(pick)
|
|
return true
|
|
|
|
paint_action = PaintAction.NO_ACTION
|
|
if rectangle_button.button_pressed:
|
|
paint_action = PaintAction.RECT
|
|
elif line_button.button_pressed:
|
|
paint_action = PaintAction.LINE
|
|
elif draw_button.button_pressed:
|
|
if event.shift_pressed:
|
|
paint_action = PaintAction.LINE
|
|
if event.is_command_or_control_pressed():
|
|
paint_action = PaintAction.RECT
|
|
|
|
if event.button_index == MOUSE_BUTTON_LEFT:
|
|
paint_mode = PaintMode.PAINT
|
|
elif event.button_index == MOUSE_BUTTON_RIGHT:
|
|
paint_mode = PaintMode.ERASE
|
|
else:
|
|
return false
|
|
|
|
if (clicked or event is InputEventMouseMotion) and paint_mode != PaintMode.NO_PAINT:
|
|
if clicked:
|
|
initial_click = current_position
|
|
terrain_undo.action_index += 1
|
|
terrain_undo.action_count = 0
|
|
var type = selected_entry
|
|
|
|
if paint_action == PaintAction.LINE or paint_action == PaintAction.RECT:
|
|
# if painting as line, execution happens on release.
|
|
# prevent other painting actions from running.
|
|
pass
|
|
elif draw_button.button_pressed:
|
|
undo_manager.create_action(tr("Draw terrain") + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tilemap, true)
|
|
var cells := _get_tileset_line(prev_position, current_position, tileset)
|
|
if paint_mode == PaintMode.PAINT:
|
|
if replace_mode:
|
|
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"replace_cells", [tilemap, cells, type])
|
|
else:
|
|
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_cells", [tilemap, cells, type])
|
|
elif paint_mode == PaintMode.ERASE:
|
|
for c in cells:
|
|
terrain_undo.add_do_method(undo_manager, tilemap, &"erase_cell", [c])
|
|
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"update_terrain_cells", [tilemap, cells])
|
|
terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
|
|
undo_manager.commit_action()
|
|
terrain_undo.action_count += 1
|
|
elif fill_button.button_pressed:
|
|
var cells := _get_fill_cells(current_position)
|
|
undo_manager.create_action(tr("Fill terrain"), UndoRedo.MERGE_DISABLE, tilemap)
|
|
if paint_mode == PaintMode.PAINT:
|
|
if replace_mode:
|
|
undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type)
|
|
else:
|
|
undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type)
|
|
elif paint_mode == PaintMode.ERASE:
|
|
for c in cells:
|
|
undo_manager.add_do_method(tilemap, &"erase_cell", c)
|
|
undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells)
|
|
terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
|
|
undo_manager.commit_action()
|
|
|
|
update_overlay.emit()
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
func canvas_mouse_exit() -> void:
|
|
draw_overlay = false
|
|
update_overlay.emit()
|
|
|
|
|
|
func _shortcut_input(event) -> void:
|
|
if event is InputEventKey:
|
|
if event.keycode == KEY_C and (event.is_command_or_control_pressed() and not event.echo):
|
|
get_viewport().set_input_as_handled()
|
|
tile_view.copy_selection()
|
|
if event.keycode == KEY_V and (event.is_command_or_control_pressed() and not event.echo):
|
|
get_viewport().set_input_as_handled()
|
|
tile_view.paste_selection()
|
|
|
|
|
|
## bresenham alg ported from Geometry2D::bresenham_line()
|
|
func _get_line(from:Vector2i, to:Vector2i) -> Array[Vector2i]:
|
|
if from == to:
|
|
return [to]
|
|
|
|
var points:Array[Vector2i] = []
|
|
var delta := (to - from).abs() * 2
|
|
var step := (to - from).sign()
|
|
var current := from
|
|
|
|
if delta.x > delta.y:
|
|
var err:int = delta.x / 2
|
|
while current.x != to.x:
|
|
points.push_back(current);
|
|
err -= delta.y
|
|
if err < 0:
|
|
current.y += step.y
|
|
err += delta.x
|
|
current.x += step.x
|
|
else:
|
|
var err:int = delta.y / 2
|
|
while current.y != to.y:
|
|
points.push_back(current)
|
|
err -= delta.x
|
|
if err < 0:
|
|
current.x += step.x
|
|
err += delta.y
|
|
current.y += step.y
|
|
|
|
points.push_back(current);
|
|
return points;
|
|
|
|
|
|
## half-offset bresenham alg ported from TileMapEditor::get_line
|
|
func _get_tileset_line(from:Vector2i, to:Vector2i, tileset:TileSet) -> Array[Vector2i]:
|
|
if tileset.tile_shape == TileSet.TILE_SHAPE_SQUARE:
|
|
return _get_line(from, to)
|
|
|
|
var points:Array[Vector2i] = []
|
|
|
|
var transposed := tileset.get_tile_offset_axis() == TileSet.TILE_OFFSET_AXIS_VERTICAL
|
|
if transposed:
|
|
from = Vector2i(from.y, from.x)
|
|
to = Vector2i(to.y, to.x)
|
|
|
|
var delta:Vector2i = to - from
|
|
delta = Vector2i(2 * delta.x + abs(posmod(to.y, 2)) - abs(posmod(from.y, 2)), delta.y)
|
|
var sign:Vector2i = delta.sign()
|
|
|
|
var current := from;
|
|
points.push_back(Vector2i(current.y, current.x) if transposed else current)
|
|
|
|
var err := 0
|
|
if abs(delta.y) < abs(delta.x):
|
|
var err_step:Vector2i = 3 * delta.abs()
|
|
while current != to:
|
|
err += err_step.y
|
|
if err > abs(delta.x):
|
|
if sign.x == 0:
|
|
current += Vector2i(sign.y, 0)
|
|
else:
|
|
current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y)
|
|
err -= err_step.x
|
|
else:
|
|
current += Vector2i(sign.x, 0)
|
|
err += err_step.y
|
|
points.push_back(Vector2i(current.y, current.x) if transposed else current)
|
|
else:
|
|
var err_step:Vector2i = delta.abs()
|
|
while current != to:
|
|
err += err_step.x
|
|
if err > 0:
|
|
if sign.x == 0:
|
|
current += Vector2i(0, sign.y)
|
|
else:
|
|
current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y)
|
|
err -= err_step.y;
|
|
else:
|
|
if sign.x == 0:
|
|
current += Vector2i(0, sign.y)
|
|
else:
|
|
current += Vector2i(-sign.x if bool(current.y % 2) != (sign.x > 0) else 0, sign.y)
|
|
err += err_step.y
|
|
points.push_back(Vector2i(current.y, current.x) if transposed else current)
|
|
|
|
return points
|
|
|
|
|
|
func _on_terrain_enable_id_pressed(id):
|
|
if id in [SourceSelectors.ALL, SourceSelectors.NONE]:
|
|
for i in source_selector_popup.item_count:
|
|
if source_selector_popup.is_item_checkable(i):
|
|
source_selector_popup.set_item_checked(i, id == SourceSelectors.ALL)
|
|
else:
|
|
var index = source_selector_popup.get_item_index(id)
|
|
var checked = source_selector_popup.is_item_checked(index)
|
|
source_selector_popup.set_item_checked(index, !checked)
|
|
|
|
var disabled_sources : Array[int]
|
|
for i in source_selector_popup.item_count:
|
|
if source_selector_popup.is_item_checkable(i) and !source_selector_popup.is_item_checked(i):
|
|
disabled_sources.append(source_selector_popup.get_item_id(i))
|
|
tile_view.disabled_sources = disabled_sources
|
|
|
|
|
|
func corresponding_tilemap_editor_button(similar: Button) -> Button:
|
|
var editors = EditorInterface.get_base_control().find_children("*", "TileMapLayerEditor", true, false)
|
|
var tile_map_layer_editor = editors[0]
|
|
var buttons = tile_map_layer_editor.find_children("*", "Button", true, false)
|
|
for button: Button in buttons:
|
|
if button.icon == similar.icon:
|
|
return button
|
|
return null
|
|
|
|
|
|
func _on_layer_up_or_down_pressed(button: Button) -> void:
|
|
var matching_button = corresponding_tilemap_editor_button(button)
|
|
if !matching_button:
|
|
return
|
|
|
|
# Major hack, to reduce flicker hide the tileset editor briefly
|
|
var editors = EditorInterface.get_base_control().find_children("*", "TileSetEditor", true, false)
|
|
var tile_set_editor = editors[0]
|
|
|
|
matching_button.pressed.emit()
|
|
tile_set_editor.modulate = Color.TRANSPARENT
|
|
await get_tree().process_frame
|
|
await get_tree().process_frame
|
|
force_show_terrains.emit()
|
|
tile_set_editor.modulate = Color.WHITE
|
|
|
|
|
|
|
|
func _on_layer_up_pressed() -> void:
|
|
_on_layer_up_or_down_pressed(layer_up)
|
|
|
|
|
|
func _on_layer_down_pressed() -> void:
|
|
_on_layer_up_or_down_pressed(layer_down)
|
|
|
|
|
|
func _on_layer_highlight_toggled(toggled: bool) -> void:
|
|
var settings = EditorInterface.get_editor_settings()
|
|
settings.set_setting("editors/tiles_editor/highlight_selected_layer", toggled)
|
|
|
|
var highlight = corresponding_tilemap_editor_button(layer_highlight)
|
|
if highlight:
|
|
highlight.toggled.emit(toggled)
|
|
|
|
|
|
func _on_layer_grid_toggled(toggled: bool) -> void:
|
|
var settings = EditorInterface.get_editor_settings()
|
|
settings.set_setting("editors/tiles_editor/display_grid", toggled)
|
|
|
|
var grid = corresponding_tilemap_editor_button(layer_grid)
|
|
if grid:
|
|
grid.toggled.emit(toggled)
|