351 lines
9.8 KiB
GDScript
351 lines
9.8 KiB
GDScript
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)
|
|
var positions = []
|
|
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_name := tileset.tile_get_name(id)
|
|
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, cursor_pos, get_active_tile())
|
|
Tool.TILE_FILL:
|
|
if placing_layer != null:
|
|
flood_fill(placing_layer, cursor_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
|