class_name PresentationCueDefinition extends Resource const ALLOWED_PARAMETER_KEYS := { "visual_scale": true, "body_scale": true, "fluff_scale": true, "show_horns": true, "show_beard": true, } @export var cue_id: StringName @export_file("*.tscn") var scene_path: String @export_file var icon_path: String @export_file var material_path: String @export var animation_hook_id: StringName @export var parameters: Dictionary = {} func validate_metadata() -> Array[String]: var errors: Array[String] = [] if not AnimalDefinition.is_valid_stable_id(cue_id): errors.append("cue_id is invalid") _validate_optional_path(scene_path, "scene_path", ["tscn", "scn"], errors) _validate_optional_path(icon_path, "icon_path", ["png", "svg", "webp"], errors) _validate_optional_path(material_path, "material_path", ["tres", "res", "material"], errors) if ( not animation_hook_id.is_empty() and not AnimalDefinition.is_valid_stable_id(animation_hook_id) ): errors.append("animation_hook_id is invalid for '%s'" % cue_id) _validate_parameters(errors) return errors func validate_assets() -> Array[String]: var errors := validate_metadata() if not errors.is_empty(): return errors for path_field in [ ["scene_path", scene_path, "PackedScene"], ["icon_path", icon_path, "Texture2D"], ["material_path", material_path, "Material"], ]: var path: String = path_field[1] if not path.is_empty() and not ResourceLoader.exists(path, path_field[2]): errors.append("%s does not resolve for '%s'" % [path_field[0], cue_id]) return errors func get_parameters() -> Dictionary: return parameters.duplicate(true) func instantiate_scene() -> Node: if scene_path.is_empty(): return null var scene := load(scene_path) as PackedScene return scene.instantiate() if scene != null else null func _validate_parameters(errors: Array[String]) -> void: for raw_key in parameters: if raw_key is not String and raw_key is not StringName: errors.append("parameter key must be a string for '%s'" % cue_id) continue var key := String(raw_key) if not ALLOWED_PARAMETER_KEYS.has(key): errors.append("unsupported parameter '%s' for '%s'" % [key, cue_id]) continue var value: Variant = parameters[raw_key] if key.ends_with("_scale"): if value is not Vector3 or not _is_positive_vector(value): errors.append("parameter '%s' must be a positive Vector3 for '%s'" % [key, cue_id]) elif value is not bool: errors.append("parameter '%s' must be bool for '%s'" % [key, cue_id]) func _validate_optional_path( path: String, field: String, extensions: Array[String], errors: Array[String] ) -> void: if path.is_empty(): return if not path.begins_with("res://") or path.get_extension().to_lower() not in extensions: errors.append("%s is invalid for '%s'" % [field, cue_id]) func _is_positive_vector(value: Vector3) -> bool: return value.is_finite() and value.x > 0.0 and value.y > 0.0 and value.z > 0.0