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

182 lines
6.5 KiB
GDScript

@tool
extends RefCounted
const ErrorCodes := preload("res://addons/godot_ai/utils/error_codes.gd")
## Creates an Environment (+ optional Sky + ProceduralSkyMaterial) chain and
## either assigns it to a WorldEnvironment node or saves it to a .tres file.
## Bundles sub-resource creation + assignment in a single undo action.
const ResourceHandler := preload("res://addons/godot_ai/handlers/resource_handler.gd")
var _undo_redo: EditorUndoRedoManager
var _connection: McpConnection
func _init(undo_redo: EditorUndoRedoManager, connection: McpConnection = null) -> void:
_undo_redo = undo_redo
_connection = connection
const _PRESETS := {
"default": {"sky": true, "fog": false},
"clear": {"sky": true, "fog": false},
"sunset": {"sky": true, "fog": false},
"night": {"sky": true, "fog": false},
"fog": {"sky": true, "fog": true},
}
func create_environment(params: Dictionary) -> Dictionary:
var node_path: String = params.get("path", "")
var resource_path: String = params.get("resource_path", "")
var overwrite: bool = params.get("overwrite", false)
var preset: String = params.get("preset", "default")
var properties: Dictionary = params.get("properties", {})
var sky_param = params.get("sky", null) # nullable — falls back to preset default
# environment_create targets the whole WorldEnvironment node (no separate
# `property` param) — pass require_property=false.
var home_err := McpResourceIO.validate_home(params, false)
if home_err != null:
return home_err
if not _PRESETS.has(preset):
return ErrorCodes.make(
ErrorCodes.VALUE_OUT_OF_RANGE,
"Invalid preset '%s'. Valid: %s" % [preset, ", ".join(_PRESETS.keys())]
)
var preset_config: Dictionary = _PRESETS[preset]
var want_sky: bool = preset_config.sky
var sky_properties: Dictionary = {}
if sky_param != null:
if sky_param is bool:
want_sky = sky_param
elif sky_param is Dictionary:
var sky_config: Dictionary = (sky_param as Dictionary).duplicate()
var material_type: String = String(sky_config.get("sky_material", "procedural")).to_lower()
if material_type != "procedural":
return ErrorCodes.make(
ErrorCodes.INVALID_PARAMS,
"sky.sky_material must be 'procedural' when sky is a dictionary"
)
sky_config.erase("sky_material")
sky_properties = sky_config
want_sky = true
else:
return ErrorCodes.make(
ErrorCodes.WRONG_TYPE,
"sky must be a bool, null, or dictionary of ProceduralSkyMaterial properties"
)
var env := Environment.new()
var sky: Sky = null
var sky_material: ProceduralSkyMaterial = null
if want_sky:
sky_material = ProceduralSkyMaterial.new()
sky = Sky.new()
sky.sky_material = sky_material
env.background_mode = Environment.BG_SKY
env.sky = sky
else:
env.background_mode = Environment.BG_CLEAR_COLOR
_apply_preset(env, sky_material, preset)
if not sky_properties.is_empty():
var sky_apply_err := ResourceHandler._apply_resource_properties(sky_material, sky_properties)
if sky_apply_err != null:
return sky_apply_err
if preset_config.fog:
env.volumetric_fog_enabled = true
env.volumetric_fog_density = 0.03
if not properties.is_empty():
var apply_err := ResourceHandler._apply_resource_properties(env, properties)
if apply_err != null:
return apply_err
if not resource_path.is_empty():
return _save_environment(env, sky, sky_material, resource_path, overwrite, preset)
return _assign_environment(env, sky, sky_material, node_path, preset)
static func _apply_preset(env: Environment, sky_material: ProceduralSkyMaterial, preset: String) -> void:
match preset:
"default", "clear":
if sky_material != null:
sky_material.sky_top_color = Color(0.38, 0.45, 0.55)
sky_material.sky_horizon_color = Color(0.65, 0.67, 0.7)
sky_material.ground_horizon_color = Color(0.65, 0.67, 0.7)
sky_material.ground_bottom_color = Color(0.2, 0.17, 0.13)
sky_material.sun_angle_max = 30.0
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ambient_light_energy = 1.0
"sunset":
if sky_material != null:
sky_material.sky_top_color = Color(0.25, 0.3, 0.55)
sky_material.sky_horizon_color = Color(1.0, 0.55, 0.3)
sky_material.ground_horizon_color = Color(0.85, 0.4, 0.25)
sky_material.ground_bottom_color = Color(0.2, 0.12, 0.1)
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ambient_light_color = Color(1.0, 0.75, 0.55)
env.ambient_light_energy = 0.8
"night":
if sky_material != null:
sky_material.sky_top_color = Color(0.02, 0.02, 0.07)
sky_material.sky_horizon_color = Color(0.05, 0.07, 0.15)
sky_material.ground_horizon_color = Color(0.04, 0.05, 0.1)
sky_material.ground_bottom_color = Color(0.0, 0.0, 0.02)
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.2, 0.22, 0.35)
env.ambient_light_energy = 0.4
"fog":
if sky_material != null:
sky_material.sky_top_color = Color(0.65, 0.65, 0.7)
sky_material.sky_horizon_color = Color(0.8, 0.8, 0.82)
sky_material.ground_horizon_color = Color(0.7, 0.7, 0.72)
sky_material.ground_bottom_color = Color(0.3, 0.3, 0.32)
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ambient_light_energy = 0.7
func _assign_environment(env: Environment, sky: Sky, sky_material: ProceduralSkyMaterial, node_path: String, preset: String) -> Dictionary:
var _resolved := McpNodeValidator.resolve_or_error(node_path, "node_path")
if _resolved.has("error"):
return _resolved
var node: Node = _resolved.node
var _scene_root: Node = _resolved.scene_root
if not (node is WorldEnvironment):
return ErrorCodes.make(
ErrorCodes.WRONG_TYPE,
"Node at %s is %s — must be WorldEnvironment" % [node_path, node.get_class()]
)
var old_env = (node as WorldEnvironment).environment
_undo_redo.create_action("MCP: Create Environment (%s) for %s" % [preset, node.name])
_undo_redo.add_do_property(node, "environment", env)
_undo_redo.add_undo_property(node, "environment", old_env)
_undo_redo.add_do_reference(env)
if sky != null:
_undo_redo.add_do_reference(sky)
if sky_material != null:
_undo_redo.add_do_reference(sky_material)
_undo_redo.commit_action()
return {
"data": {
"path": node_path,
"preset": preset,
"sky_created": sky != null,
"sky_material_class": sky_material.get_class() if sky_material != null else "",
"undoable": true,
}
}
func _save_environment(env: Environment, _sky: Sky, _sky_material: ProceduralSkyMaterial, resource_path: String, overwrite: bool, preset: String) -> Dictionary:
return McpResourceIO.save_to_disk(env, resource_path, overwrite, "Environment", {
"preset": preset,
}, _connection)