382 lines
12 KiB
GDScript
382 lines
12 KiB
GDScript
@tool
|
|
extends "res://addons/godot_mcp/commands/base_command.gd"
|
|
|
|
|
|
func get_commands() -> Dictionary:
|
|
return {
|
|
"tilemap_set_cell": _tilemap_set_cell,
|
|
"tilemap_fill_rect": _tilemap_fill_rect,
|
|
"tilemap_get_cell": _tilemap_get_cell,
|
|
"tilemap_clear": _tilemap_clear,
|
|
"tilemap_get_info": _tilemap_get_info,
|
|
"tilemap_get_used_cells": _tilemap_get_used_cells,
|
|
}
|
|
|
|
|
|
func _find_tilemap_node(node_path: String) -> Node:
|
|
var node := find_node_by_path(node_path)
|
|
if node is TileMapLayer or _is_legacy_tilemap(node):
|
|
return node
|
|
return null
|
|
|
|
|
|
func _is_legacy_tilemap(node: Node) -> bool:
|
|
return node != null and node.get_class() == "TileMap"
|
|
|
|
|
|
func _not_found_result(node_path: String) -> Dictionary:
|
|
return error_not_found(
|
|
"TileMapLayer or TileMap at '%s'" % node_path,
|
|
"Use TileMapLayer for current Godot projects, or pass a deprecated TileMap node with optional layer."
|
|
)
|
|
|
|
|
|
func _get_single_layer(tilemap: Node, params: Dictionary) -> Array:
|
|
if tilemap is TileMapLayer:
|
|
if params.has("layer") and int(params["layer"]) != 0:
|
|
return [0, error_invalid_params("layer only applies to deprecated TileMap nodes; TileMapLayer has one implicit layer")]
|
|
return [0, null]
|
|
|
|
var layer: int = optional_int(params, "layer", 0)
|
|
var layer_error := _validate_tilemap_layer(tilemap, layer)
|
|
if not layer_error.is_empty():
|
|
return [layer, layer_error]
|
|
return [layer, null]
|
|
|
|
|
|
func _get_clear_layers(tilemap: Node, params: Dictionary) -> Array:
|
|
if tilemap is TileMapLayer:
|
|
var layer_result := _get_single_layer(tilemap, params)
|
|
if layer_result[1] != null:
|
|
return [[], layer_result[1]]
|
|
return [[0], null]
|
|
|
|
if params.has("layer"):
|
|
var layer_result := _get_single_layer(tilemap, params)
|
|
if layer_result[1] != null:
|
|
return [[], layer_result[1]]
|
|
return [[int(layer_result[0])], null]
|
|
|
|
var layers: Array = []
|
|
for layer in range(_get_tilemap_layer_count(tilemap)):
|
|
layers.append(layer)
|
|
return [layers, null]
|
|
|
|
|
|
func _validate_tilemap_layer(tilemap: Node, layer: int) -> Dictionary:
|
|
if tilemap is TileMapLayer:
|
|
return {}
|
|
|
|
var layer_count := _get_tilemap_layer_count(tilemap)
|
|
if layer_count <= 0:
|
|
return error_invalid_params("TileMap has no layers")
|
|
if layer < -layer_count or layer >= layer_count:
|
|
return error_invalid_params("layer %d is out of range for TileMap with %d layers" % [layer, layer_count])
|
|
return {}
|
|
|
|
|
|
func _get_tilemap_layer_count(tilemap: Node) -> int:
|
|
if tilemap is TileMapLayer:
|
|
return 1
|
|
return int(tilemap.call("get_layers_count"))
|
|
|
|
|
|
func _get_used_cells(tilemap: Node, layer: int) -> Array:
|
|
if tilemap is TileMapLayer:
|
|
return tilemap.call("get_used_cells")
|
|
return tilemap.call("get_used_cells", layer)
|
|
|
|
|
|
func _get_cell_source_id(tilemap: Node, layer: int, coords: Vector2i) -> int:
|
|
if tilemap is TileMapLayer:
|
|
return int(tilemap.call("get_cell_source_id", coords))
|
|
return int(tilemap.call("get_cell_source_id", layer, coords))
|
|
|
|
|
|
func _get_cell_atlas_coords(tilemap: Node, layer: int, coords: Vector2i) -> Vector2i:
|
|
if tilemap is TileMapLayer:
|
|
return tilemap.call("get_cell_atlas_coords", coords)
|
|
return tilemap.call("get_cell_atlas_coords", layer, coords)
|
|
|
|
|
|
func _get_cell_alternative_tile(tilemap: Node, layer: int, coords: Vector2i) -> int:
|
|
if tilemap is TileMapLayer:
|
|
return int(tilemap.call("get_cell_alternative_tile", coords))
|
|
return int(tilemap.call("get_cell_alternative_tile", layer, coords))
|
|
|
|
|
|
func _tilemap_set_cell(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "node_path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var node_path: String = result[0]
|
|
|
|
var tilemap := _find_tilemap_node(node_path)
|
|
if tilemap == null:
|
|
return _not_found_result(node_path)
|
|
|
|
var layer_result := _get_single_layer(tilemap, params)
|
|
if layer_result[1] != null:
|
|
return layer_result[1]
|
|
var layer: int = layer_result[0]
|
|
|
|
var x: int = int(params.get("x", 0))
|
|
var y: int = int(params.get("y", 0))
|
|
var source_id: int = int(params.get("source_id", 0))
|
|
var atlas_x: int = int(params.get("atlas_x", 0))
|
|
var atlas_y: int = int(params.get("atlas_y", 0))
|
|
var alternative: int = int(params.get("alternative", 0))
|
|
|
|
var coords := Vector2i(x, y)
|
|
var old_cells := [_capture_cell(tilemap, layer, coords)]
|
|
var new_cells := [_make_cell(layer, coords, source_id, Vector2i(atlas_x, atlas_y), alternative)]
|
|
|
|
var undo_redo := get_undo_redo()
|
|
undo_redo.create_action("MCP: Set TileMap cell")
|
|
_add_do_set_cells(undo_redo, tilemap, new_cells)
|
|
_add_undo_set_cells(undo_redo, tilemap, old_cells)
|
|
undo_redo.commit_action()
|
|
|
|
return success({"x": x, "y": y, "layer": layer, "node_class": tilemap.get_class(), "source_id": source_id, "atlas_coords": [atlas_x, atlas_y]})
|
|
|
|
|
|
func _tilemap_fill_rect(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "node_path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var node_path: String = result[0]
|
|
|
|
var tilemap := _find_tilemap_node(node_path)
|
|
if tilemap == null:
|
|
return _not_found_result(node_path)
|
|
|
|
var layer_result := _get_single_layer(tilemap, params)
|
|
if layer_result[1] != null:
|
|
return layer_result[1]
|
|
var layer: int = layer_result[0]
|
|
|
|
var x1: int = int(params.get("x1", 0))
|
|
var y1: int = int(params.get("y1", 0))
|
|
var x2: int = int(params.get("x2", 0))
|
|
var y2: int = int(params.get("y2", 0))
|
|
var source_id: int = int(params.get("source_id", 0))
|
|
var atlas_x: int = int(params.get("atlas_x", 0))
|
|
var atlas_y: int = int(params.get("atlas_y", 0))
|
|
var alternative: int = int(params.get("alternative", 0))
|
|
|
|
var count := 0
|
|
var old_cells: Array = []
|
|
var new_cells: Array = []
|
|
for cx in range(mini(x1, x2), maxi(x1, x2) + 1):
|
|
for cy in range(mini(y1, y2), maxi(y1, y2) + 1):
|
|
var coords := Vector2i(cx, cy)
|
|
old_cells.append(_capture_cell(tilemap, layer, coords))
|
|
new_cells.append(_make_cell(layer, coords, source_id, Vector2i(atlas_x, atlas_y), alternative))
|
|
count += 1
|
|
|
|
var undo_redo := get_undo_redo()
|
|
undo_redo.create_action("MCP: Fill TileMap rect")
|
|
_add_do_set_cells(undo_redo, tilemap, new_cells)
|
|
_add_undo_set_cells(undo_redo, tilemap, old_cells)
|
|
undo_redo.commit_action()
|
|
|
|
return success({"filled": count, "rect": [x1, y1, x2, y2], "layer": layer, "node_class": tilemap.get_class()})
|
|
|
|
|
|
func _tilemap_get_cell(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "node_path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var node_path: String = result[0]
|
|
|
|
var tilemap := _find_tilemap_node(node_path)
|
|
if tilemap == null:
|
|
return _not_found_result(node_path)
|
|
|
|
var layer_result := _get_single_layer(tilemap, params)
|
|
if layer_result[1] != null:
|
|
return layer_result[1]
|
|
var layer: int = layer_result[0]
|
|
|
|
var x: int = int(params.get("x", 0))
|
|
var y: int = int(params.get("y", 0))
|
|
var coords := Vector2i(x, y)
|
|
|
|
var source_id := _get_cell_source_id(tilemap, layer, coords)
|
|
var atlas_coords := _get_cell_atlas_coords(tilemap, layer, coords)
|
|
var alternative := _get_cell_alternative_tile(tilemap, layer, coords)
|
|
|
|
return success({
|
|
"x": x, "y": y,
|
|
"layer": layer,
|
|
"node_class": tilemap.get_class(),
|
|
"source_id": source_id,
|
|
"atlas_coords": [atlas_coords.x, atlas_coords.y],
|
|
"alternative": alternative,
|
|
"empty": source_id == -1,
|
|
})
|
|
|
|
|
|
func _tilemap_clear(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "node_path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var node_path: String = result[0]
|
|
|
|
var tilemap := _find_tilemap_node(node_path)
|
|
if tilemap == null:
|
|
return _not_found_result(node_path)
|
|
|
|
var clear_layers_result := _get_clear_layers(tilemap, params)
|
|
if clear_layers_result[1] != null:
|
|
return clear_layers_result[1]
|
|
var layers: Array = clear_layers_result[0]
|
|
|
|
var old_cells := _capture_cells(tilemap, layers)
|
|
|
|
var undo_redo := get_undo_redo()
|
|
undo_redo.create_action("MCP: Clear TileMap")
|
|
_add_do_clear(undo_redo, tilemap, layers)
|
|
_add_undo_set_cells(undo_redo, tilemap, old_cells)
|
|
undo_redo.commit_action()
|
|
return success({"cleared": true, "layers": layers, "node_class": tilemap.get_class()})
|
|
|
|
|
|
func _make_cell(layer: int, coords: Vector2i, source_id: int, atlas_coords: Vector2i, alternative: int) -> Dictionary:
|
|
return {
|
|
"layer": layer,
|
|
"coords": coords,
|
|
"source_id": source_id,
|
|
"atlas_coords": atlas_coords,
|
|
"alternative": alternative,
|
|
}
|
|
|
|
|
|
func _capture_cell(tilemap: Node, layer: int, coords: Vector2i) -> Dictionary:
|
|
return _make_cell(
|
|
layer,
|
|
coords,
|
|
_get_cell_source_id(tilemap, layer, coords),
|
|
_get_cell_atlas_coords(tilemap, layer, coords),
|
|
_get_cell_alternative_tile(tilemap, layer, coords)
|
|
)
|
|
|
|
|
|
func _capture_cells(tilemap: Node, layers: Array) -> Array:
|
|
var cells: Array = []
|
|
for layer: int in layers:
|
|
for coords: Vector2i in _get_used_cells(tilemap, layer):
|
|
cells.append(_capture_cell(tilemap, layer, coords))
|
|
return cells
|
|
|
|
|
|
func _add_do_set_cells(undo_redo: EditorUndoRedoManager, tilemap: Node, cells: Array) -> void:
|
|
for cell: Dictionary in cells:
|
|
if tilemap is TileMapLayer:
|
|
undo_redo.add_do_method(tilemap, "set_cell", cell["coords"], cell["source_id"], cell["atlas_coords"], cell["alternative"])
|
|
else:
|
|
undo_redo.add_do_method(tilemap, "set_cell", cell["layer"], cell["coords"], cell["source_id"], cell["atlas_coords"], cell["alternative"])
|
|
|
|
|
|
func _add_undo_set_cells(undo_redo: EditorUndoRedoManager, tilemap: Node, cells: Array) -> void:
|
|
for cell: Dictionary in cells:
|
|
if tilemap is TileMapLayer:
|
|
undo_redo.add_undo_method(tilemap, "set_cell", cell["coords"], cell["source_id"], cell["atlas_coords"], cell["alternative"])
|
|
else:
|
|
undo_redo.add_undo_method(tilemap, "set_cell", cell["layer"], cell["coords"], cell["source_id"], cell["atlas_coords"], cell["alternative"])
|
|
|
|
|
|
func _add_do_clear(undo_redo: EditorUndoRedoManager, tilemap: Node, layers: Array) -> void:
|
|
if tilemap is TileMapLayer or layers.size() == _get_tilemap_layer_count(tilemap):
|
|
undo_redo.add_do_method(tilemap, "clear")
|
|
return
|
|
|
|
for layer: int in layers:
|
|
undo_redo.add_do_method(tilemap, "clear_layer", layer)
|
|
|
|
|
|
func _tilemap_get_info(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "node_path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var node_path: String = result[0]
|
|
|
|
var tilemap := _find_tilemap_node(node_path)
|
|
if tilemap == null:
|
|
return _not_found_result(node_path)
|
|
|
|
var tile_set: TileSet = tilemap.get("tile_set")
|
|
var sources: Array = []
|
|
if tile_set:
|
|
for i in tile_set.get_source_count():
|
|
var source_id := tile_set.get_source_id(i)
|
|
var source := tile_set.get_source(source_id)
|
|
var info := {"id": source_id, "type": source.get_class()}
|
|
if source is TileSetAtlasSource:
|
|
var atlas: TileSetAtlasSource = source
|
|
info["texture"] = atlas.texture.resource_path if atlas.texture else ""
|
|
info["tile_count"] = atlas.get_tiles_count()
|
|
sources.append(info)
|
|
|
|
var layers := _get_layer_info(tilemap)
|
|
var used_cells := 0
|
|
for layer_info: Dictionary in layers:
|
|
used_cells += int(layer_info["used_cells"])
|
|
|
|
return success({
|
|
"node_path": node_path,
|
|
"node_class": tilemap.get_class(),
|
|
"layer_count": layers.size(),
|
|
"layers": layers,
|
|
"used_cells": used_cells,
|
|
"tile_set_sources": sources,
|
|
"tile_size": [tile_set.tile_size.x, tile_set.tile_size.y] if tile_set else [0, 0],
|
|
})
|
|
|
|
|
|
func _get_layer_info(tilemap: Node) -> Array:
|
|
var layers: Array = []
|
|
if tilemap is TileMapLayer:
|
|
layers.append({
|
|
"index": 0,
|
|
"name": tilemap.name,
|
|
"enabled": true,
|
|
"used_cells": _get_used_cells(tilemap, 0).size(),
|
|
})
|
|
return layers
|
|
|
|
for layer in range(_get_tilemap_layer_count(tilemap)):
|
|
layers.append({
|
|
"index": layer,
|
|
"name": String(tilemap.call("get_layer_name", layer)),
|
|
"enabled": bool(tilemap.call("is_layer_enabled", layer)),
|
|
"used_cells": _get_used_cells(tilemap, layer).size(),
|
|
})
|
|
return layers
|
|
|
|
|
|
func _tilemap_get_used_cells(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "node_path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var node_path: String = result[0]
|
|
|
|
var tilemap := _find_tilemap_node(node_path)
|
|
if tilemap == null:
|
|
return _not_found_result(node_path)
|
|
|
|
var layer_result := _get_single_layer(tilemap, params)
|
|
if layer_result[1] != null:
|
|
return layer_result[1]
|
|
var layer: int = layer_result[0]
|
|
|
|
var max_count: int = maxi(0, optional_int(params, "max_count", 500))
|
|
var cells: Array = []
|
|
var used := _get_used_cells(tilemap, layer)
|
|
|
|
for i in mini(used.size(), max_count):
|
|
var pos: Vector2i = used[i]
|
|
cells.append({"x": pos.x, "y": pos.y, "layer": layer, "source_id": _get_cell_source_id(tilemap, layer, pos)})
|
|
|
|
return success({"cells": cells, "total": used.size(), "returned": cells.size(), "layer": layer, "node_class": tilemap.get_class()})
|