Files
tekton/addons/godot_ai/handlers/tileset_handler.gd
T

175 lines
5.6 KiB
GDScript

@tool
extends RefCounted
## TileSet management — atlas inspection helpers.
const ErrorCodes := preload("res://addons/godot_ai/utils/error_codes.gd")
func _init() -> void:
pass
## Query all occupied atlas tile positions for a single source.
##
## params:
## tileset_path — res:// path to the TileSet resource (required, non-empty)
## source_id — raw TileSet source id (required)
##
## Returns:
## {"data": {"tiles": [{"col": int, "row": int}, ...], "count": int}}
## on success (including empty sources, where tiles=[] and count=0)
## ErrorCodes.make(code, message) on any validation or load failure
##
## Error codes:
## MISSING_REQUIRED_PARAM — tileset_path absent/empty, or source_id absent
## RESOURCE_NOT_FOUND — ResourceLoader.exists(tileset_path) is false
## WRONG_TYPE — loaded resource is not a TileSet, or source is
## not a TileSetAtlasSource
## VALUE_OUT_OF_RANGE — source_id not present in TileSet
##
## This method is read-only: it never calls ResourceSaver or modifies any resource.
func get_atlas_tiles(params: Dictionary) -> Dictionary:
var resolved := _resolve_atlas_source(params)
if resolved.has("error"):
return resolved
var src: TileSetAtlasSource = resolved.src
var tiles: Array = []
for i in range(src.get_tiles_count()):
var v: Vector2i = src.get_tile_id(i)
tiles.append({"col": v.x, "row": v.y})
return {"data": {"tiles": tiles, "count": tiles.size()}}
## Return the atlas texture of a TileSetAtlasSource as a Base64-encoded PNG.
##
## params:
## tileset_path — res:// path to the TileSet resource (required, non-empty)
## source_id — raw TileSet source id (required)
## max_size — optional int; if > 0, the image is scaled so its longest
## edge is at most max_size pixels (default 0 = full res)
##
## Returns:
## {"data": {"image_base64": String, "width": int, "height": int,
## "original_width": int, "original_height": int, "format": "png"}}
## on success
## ErrorCodes.make(code, message) on any validation or load failure
##
## Error codes:
## MISSING_REQUIRED_PARAM — tileset_path absent/empty, or source_id absent
## RESOURCE_NOT_FOUND — ResourceLoader.exists(tileset_path) is false
## WRONG_TYPE — loaded resource is not a TileSet, or source is
## not a TileSetAtlasSource, or texture is null
## VALUE_OUT_OF_RANGE — source_id not present in TileSet
##
## This method is read-only: it never calls ResourceSaver or modifies anything.
func get_atlas_image(params: Dictionary) -> Dictionary:
var resolved := _resolve_atlas_source(params)
if resolved.has("error"):
return resolved
var source_id: int = resolved.source_id
var src: TileSetAtlasSource = resolved.src
var tex: Texture2D = src.texture
if tex == null:
return ErrorCodes.make(
ErrorCodes.WRONG_TYPE,
"Source %d has no texture assigned" % source_id
)
var img: Image = tex.get_image()
if img == null:
return ErrorCodes.make(
ErrorCodes.WRONG_TYPE,
"Could not retrieve image data from texture of source %d" % source_id
)
if img.is_compressed():
var decompress_err := img.decompress()
if decompress_err != OK:
return ErrorCodes.make(
ErrorCodes.INTERNAL_ERROR,
"Could not decompress texture of source %d: %s" % [source_id, error_string(decompress_err)]
)
var original_width: int = img.get_width()
var original_height: int = img.get_height()
var max_size: int = params.get("max_size", 0)
if max_size > 0:
var longest_edge: int = max(original_width, original_height)
if longest_edge > max_size:
var scale: float = float(max_size) / float(longest_edge)
var new_w: int = max(1, int(original_width * scale))
var new_h: int = max(1, int(original_height * scale))
img.resize(new_w, new_h, Image.INTERPOLATE_LANCZOS)
var png_bytes: PackedByteArray = img.save_png_to_buffer()
if png_bytes.is_empty():
return ErrorCodes.make(
ErrorCodes.INTERNAL_ERROR,
"PNG encoding produced empty output for source %d" % source_id
)
var b64: String = Marshalls.raw_to_base64(png_bytes)
return {
"data": {
"image_base64": b64,
"width": img.get_width(),
"height": img.get_height(),
"original_width": original_width,
"original_height": original_height,
"format": "png",
}
}
func _resolve_atlas_source(params: Dictionary) -> Dictionary:
var tileset_path: String = params.get("tileset_path", "")
if tileset_path.is_empty():
return ErrorCodes.make(
ErrorCodes.MISSING_REQUIRED_PARAM,
"'tileset_path' parameter is required and must not be empty"
)
if not params.has("source_id"):
return ErrorCodes.make(
ErrorCodes.MISSING_REQUIRED_PARAM,
"'source_id' parameter is required"
)
if not ResourceLoader.exists(tileset_path):
return ErrorCodes.make(
ErrorCodes.RESOURCE_NOT_FOUND,
"TileSet resource not found: %s" % tileset_path
)
var ts = load(tileset_path)
if not ts is TileSet:
var loaded_type := "null" if ts == null else ts.get_class()
return ErrorCodes.make(
ErrorCodes.WRONG_TYPE,
"Resource at '%s' is not a TileSet (got %s)" % [tileset_path, loaded_type]
)
var source_id: int = int(params.get("source_id", -999))
if source_id < 0 or not ts.has_source(source_id):
return ErrorCodes.make(
ErrorCodes.VALUE_OUT_OF_RANGE,
"source_id %d does not exist in TileSet" % source_id
)
var src = ts.get_source(source_id)
if not src is TileSetAtlasSource:
var source_type: String = "null" if src == null else src.get_class()
return ErrorCodes.make(
ErrorCodes.WRONG_TYPE,
"Source %d is not a TileSetAtlasSource (got %s)" % [source_id, source_type]
)
return {
"source_id": source_id,
"src": src,
}