182 lines
6.5 KiB
GDScript
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)
|