extends Control onready var map_menu := ($menu/menubar/MapMenu as MenuButton).get_popup() onready var map_node := $map onready var cursor := $map/cursor onready var cursor_sprite := $map/cursor/preview const MAP_SCALE_MAX := 8.0 const MAP_SCALE_MIN := 0.25 const TileTabScene := preload("res://Scenes/Editor/TileTab.tscn") const objects = { "Machines": [ GameObjectComputer, GameObjectDoor, GameObjectEngine, GameObjectLightbulb, GameObjectPowerStorage ] } func _ready(): # Add tiles add_tile_tab("Base", $map/tiles/base) add_tile_tab("Cables", $map/tiles/cables) add_tile_tab("Floors", $map/tiles/floor) add_tile_tab("Walls", $map/tiles/walls) # Add objects for cat in objects: add_object_list(cat, objects[cat]) enum PlacingMode { NONE, TILEMAP, OBJECT } enum Tool { NONE TILE_FREEHAND, TILE_RECT, TILE_FILL, OBJ_PLACE, OBJ_EDIT, OBJ_REMOVE } # Prevent input handler from running when other dialogs/actions are focused var input_lock := false # Drag variables var dragging := false var view_origin := Vector2.ZERO var mouse_origin := Vector2.ZERO # Placing variables var placing := false var deleting := false var placing_mode = PlacingMode.NONE var placing_layer = null var placing_tile_id := -1 var current_tool = Tool.NONE # Cursor variables var cursor_pos := Vector2.ZERO var pressed_pos := Vector2.ZERO const TILE_SIZE := 32 func _input(ev: InputEvent): if input_lock: return if ev is InputEventMouseMotion: if dragging: map_node.global_position = view_origin - (mouse_origin - ev.global_position) else: # Map cursor location to grid var tile_snap: Vector2 = map_node.scale * TILE_SIZE var mouse_offset: Vector2 = (ev.global_position - map_node.global_position) / tile_snap var new_cursor_pos := Vector2(floor(mouse_offset.x), floor(mouse_offset.y)) if new_cursor_pos != cursor_pos: var old_pos = cursor_pos cursor_pos = new_cursor_pos if placing or deleting: if current_tool != Tool.NONE: tile_mouse_move(old_pos, cursor_pos) cursor.position = cursor_pos * TILE_SIZE if ev is InputEventMouseButton: var mouse := ev as InputEventMouseButton if mouse.pressed: match ev.button_index: BUTTON_LEFT, BUTTON_RIGHT: if current_tool != Tool.NONE: tile_pressed(ev.button_index, cursor_pos) BUTTON_WHEEL_UP: # Zoom in var old_scale = map_node.scale if map_node.scale.x < MAP_SCALE_MAX: if map_node.scale.x < 1: map_node.scale *= 2 else: map_node.scale += Vector2.ONE map_node.position -= (map_node.position + mouse.position * map_node.scale)- (map_node.position + mouse.position * old_scale) BUTTON_WHEEL_DOWN: # Zoom out var old_scale = map_node.scale if map_node.scale.x > MAP_SCALE_MIN: if map_node.scale.x <= 1: map_node.scale /= 2 else: map_node.scale -= Vector2.ONE map_node.position -= (map_node.position + mouse.position * map_node.scale)- (map_node.position + mouse.position * old_scale) BUTTON_MIDDLE: view_origin = map_node.global_position mouse_origin = ev.global_position dragging = true else: match ev.button_index: BUTTON_LEFT, BUTTON_RIGHT: if current_tool != Tool.NONE: tile_released(ev.button_index, cursor_pos) BUTTON_MIDDLE: dragging = false map_node.global_position = view_origin - (mouse_origin - ev.global_position) func place_rect(a: Vector2, b: Vector2, id: int): if placing_layer == null: return var layer := placing_layer as TileMap # Sort coordinates var x_ord = Vector2(a.x, b.x) var y_ord = Vector2(a.y, b.y) if a.x > b.x: x_ord = Vector2(b.x, a.x) if a.y > b.y: y_ord = Vector2(b.y, a.y) for x in range(x_ord.x, x_ord.y+1): place_tile(layer, Vector2(x, a.y), id) place_tile(layer, Vector2(x, b.y), id) for y in range(y_ord.x, y_ord.y+1): place_tile(layer, Vector2(a.x, y), id) place_tile(layer, Vector2(b.x, y), id) layer.update_bitmask_region(a, b) func place_tile(tilemap: TileMap, pos: Vector2, id: int): # Place tile tilemap.set_cellv(pos, id) # If no base is under that tile, add it if id != -1 and tilemap != $map/tiles/base: if $map/tiles/base.get_cellv(pos) == -1: $map/tiles/base.set_cellv(pos, 1) func place_tiles(tilemap: TileMap, positions: Array, id: int): for pos in positions: place_tile(tilemap, pos, id) for pos in positions: # Update bitmask tilemap.update_bitmask_area(pos) var group := ButtonGroup.new() func add_tile_tab(name: String, tilemap: TileMap): var tab := add_tab(name) tab.connect("tile_selected", self, "_tile_selected", [name, tilemap]) var tileset := tilemap.tile_set var ids := tileset.get_tiles_ids() for id in ids: var tile_icon := make_tile_texture(tileset, id) tab.add_entry(id, group, tile_icon) func add_tab(name: String) -> TileTab: var tab := TileTabScene.instance() as TileTab tab.name = name $layers/tabs.add_child(tab) return tab func add_object_list(name: String, objects: Array): var tab := ItemList.new() tab.connect("item_selected", self, "_item_selected", [tab]) tab.name = name for obj in objects: var editor_info = obj.editor_info() tab.add_item(editor_info.name, editor_info.icon) tab.set_item_metadata(tab.get_item_count()-1, editor_info) $objects/tabs.add_child(tab) func _item_selected(idx: int, tab: ItemList): var meta = tab.get_item_metadata(idx) cursor_sprite.texture = meta.icon func _toggle_tile_input(enable: bool): input_lock = not enable func _tile_selected(id: int, _name: String, tilemap: TileMap): cursor_sprite.texture = make_tile_texture(tilemap.tile_set, id) placing_mode = PlacingMode.TILEMAP placing_layer = tilemap placing_tile_id = id func make_tile_texture(tileset: TileSet, id: int) -> AtlasTexture: var tile_mode := tileset.tile_get_tile_mode(id) var tile_icon := AtlasTexture.new() tile_icon.atlas = tileset.tile_get_texture(id) match tile_mode: TileSet.AUTO_TILE: var tile_size := tileset.autotile_get_size(id) tile_icon.region = Rect2( tileset.autotile_get_icon_coordinate(id) * tile_size, tile_size ) TileSet.SINGLE_TILE: tile_icon.region = tileset.tile_get_region(id) return tile_icon onready var layers_panel = $layers onready var objects_panel = $objects onready var brush_panel = $tools/brushPanel onready var obj_action_panel = $tools/objPanel func _tool_selected(tool_type): layers_panel.visible = false brush_panel.visible = false objects_panel.visible = false obj_action_panel.visible = false placing_mode = PlacingMode.NONE match tool_type: "tile": layers_panel.visible = tool_type(current_tool) == "brush" brush_panel.visible = true placing_mode = PlacingMode.TILEMAP "obj": obj_action_panel.visible = true objects_panel.visible = tool_type(current_tool) == "obj" placing_mode = PlacingMode.OBJECT func _set_brush(brush_type: String): match brush_type: "freehand": current_tool = Tool.TILE_FREEHAND "rect": current_tool = Tool.TILE_RECT "fill": current_tool = Tool.TILE_FILL layers_panel.visible = true func tool_type(brush): match brush: Tool.TILE_FILL, Tool.TILE_FREEHAND, Tool.TILE_RECT: return "brush" Tool.OBJ_EDIT, Tool.OBJ_PLACE, Tool.OBJ_REMOVE: return "obj" _: return "none" var ff_cells = {} var ff_origin = Vector2.ZERO const BOUNDS_MAX_X = 40 const BOUNDS_MAX_Y = 40 # Sanity check: force bounds for cell func ff_is_inbound(cell: Vector2): return abs(cell.x-ff_origin.x) < BOUNDS_MAX_X and abs(cell.y-ff_origin.y) < BOUNDS_MAX_Y func ff_is_valid(tilemap: TileMap, cell: Vector2, matching_tile: int) -> bool: return ff_is_inbound(cell) and (not ff_cells.has(cell)) and tilemap.get_cellv(cell) == matching_tile func ff_get_neighbours(tilemap: TileMap, cell: Vector2, matching_tile: int) -> Array: var neighbours = [Vector2(cell.x, cell.y-1),Vector2(cell.x-1, cell.y),Vector2(cell.x+1, cell.y), Vector2(cell.x, cell.y+1)] var out = [] for neighbour in neighbours: # Have we checked this already? if not ff_is_valid(tilemap, neighbour, matching_tile): continue out.push_back(neighbour) return out func flood_fill(tilemap: TileMap, pos: Vector2, id: int): # Reset lists ff_cells = {} ff_origin = pos var current_tile = tilemap.get_cellv(pos) var queue = ff_get_neighbours(tilemap, pos, current_tile) place_tile(tilemap, pos, id) ff_cells[pos] = true # Depth-first search while not queue.empty(): var current = queue.pop_front() # Have we checked this already? if not ff_is_valid(tilemap, current, current_tile): continue var tile_id = tilemap.get_cellv(current) if tile_id == current_tile: ff_cells[current] = true place_tile(tilemap, current, id) for neighbour in ff_get_neighbours(tilemap, current, current_tile): queue.push_front(neighbour) tilemap.update_bitmask_region() func _set_obj_action(action): match action: "add": current_tool = Tool.OBJ_PLACE "edit": current_tool = Tool.OBJ_EDIT "remove": current_tool = Tool.OBJ_REMOVE objects_panel.visible = true func tile_pressed(button: int, pos: Vector2): match button: BUTTON_LEFT: placing = true BUTTON_RIGHT: deleting = true match current_tool: Tool.TILE_FREEHAND: if placing_layer != null: place_tiles(placing_layer, [pos], get_active_tile()) Tool.OBJ_PLACE: pass pressed_pos = pos func tile_released(button: int, pos: Vector2): match current_tool: Tool.TILE_RECT: if placing_layer != null: place_rect(pressed_pos, pos, get_active_tile()) Tool.TILE_FILL: if placing_layer != null: flood_fill(placing_layer, pos, get_active_tile()) match button: BUTTON_LEFT: placing = false BUTTON_RIGHT: deleting = false func tile_mouse_move(_from: Vector2, to: Vector2): match current_tool: Tool.TILE_FREEHAND: if placing_layer != null: place_tiles(placing_layer, [to], get_active_tile()) func get_active_tile(): if deleting: return -1 return placing_tile_id