This repository has been archived on 2020-09-30. You can view files and clone it, but cannot push or open issues or pull requests.
odyssey-old/Scenes/MapEditor.gd
2020-09-24 12:59:40 +02:00

349 lines
9.7 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)
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