Files
TheTree_mock/scripts/core/definition_registry.gd
T

765 lines
28 KiB
GDScript

class_name DefinitionRegistry
extends RefCounted
## Loads, validates, and indexes immutable game definitions.
##
## Invalid definitions are reported but never exposed through lookup/query APIs.
## All returned definition and issue lists use a deterministic ID-first order.
const DEFAULT_FACILITY_DIRECTORY := "res://data/facilities"
const FACILITY_FILE_EXTENSION := "tres"
const SEVERITY_ERROR := &"error"
const SEVERITY_WARNING := &"warning"
var _facilities_by_id: Dictionary = {}
var _ordered_facility_ids: Array[StringName] = []
var _errors: Array[Dictionary] = []
var _warnings: Array[Dictionary] = []
var _source_count := 0
## Scans the default facility directory and rebuilds the registry.
func rebuild(directory_path: String = DEFAULT_FACILITY_DIRECTORY) -> Dictionary:
_reset()
var paths := _discover_facility_paths(directory_path)
if not _errors.is_empty():
_finish_rebuild()
return get_report()
_load_and_index(paths)
_finish_rebuild()
return get_report()
## Rebuilds from an explicit path list. Primarily useful for content packs and tests.
func rebuild_from_paths(paths: Array[String]) -> Dictionary:
_reset()
var ordered_paths := paths.duplicate()
ordered_paths.sort()
_load_and_index(ordered_paths)
_finish_rebuild()
return get_report()
func is_valid() -> bool:
return _errors.is_empty()
func has_fatal_errors() -> bool:
return not _errors.is_empty()
func facility_count() -> int:
return _ordered_facility_ids.size()
func source_count() -> int:
return _source_count
func has_facility(facility_id: StringName) -> bool:
return _facilities_by_id.has(facility_id)
func get_facility(facility_id: StringName) -> FacilityDefinition:
return _facilities_by_id.get(facility_id) as FacilityDefinition
func get_facility_ids() -> Array[StringName]:
return _ordered_facility_ids.duplicate()
func get_all_facilities() -> Array[FacilityDefinition]:
var result: Array[FacilityDefinition] = []
for facility_id in _ordered_facility_ids:
result.append(_facilities_by_id[facility_id] as FacilityDefinition)
return result
func query_facilities_by_tag(tag: StringName) -> Array[FacilityDefinition]:
var result: Array[FacilityDefinition] = []
for facility_id in _ordered_facility_ids:
var definition := _facilities_by_id[facility_id] as FacilityDefinition
if tag in definition.tags:
result.append(definition)
return result
func query_facilities_by_zone(zone: int) -> Array[FacilityDefinition]:
var result: Array[FacilityDefinition] = []
for facility_id in _ordered_facility_ids:
var definition := _facilities_by_id[facility_id] as FacilityDefinition
if definition.zone == zone:
result.append(definition)
return result
func query_facilities_by_layer(layer: int) -> Array[FacilityDefinition]:
var result: Array[FacilityDefinition] = []
for facility_id in _ordered_facility_ids:
var definition := _facilities_by_id[facility_id] as FacilityDefinition
if definition.layer == layer:
result.append(definition)
return result
func get_fatal_errors() -> Array[Dictionary]:
return _errors.duplicate(true)
func get_warnings() -> Array[Dictionary]:
return _warnings.duplicate(true)
func get_report() -> Dictionary:
return {
&"valid": is_valid(),
&"source_count": _source_count,
&"definition_count": facility_count(),
&"error_count": _errors.size(),
&"warning_count": _warnings.size(),
&"errors": get_fatal_errors(),
&"warnings": get_warnings(),
}
func _reset() -> void:
_facilities_by_id.clear()
_ordered_facility_ids.clear()
_errors.clear()
_warnings.clear()
_source_count = 0
func _discover_facility_paths(directory_path: String) -> Array[String]:
var result: Array[String] = []
var directory := DirAccess.open(directory_path)
if directory == null:
_add_error(
&"directory_unreadable",
&"",
directory_path,
&"resource_path",
"Facility definition directory cannot be opened."
)
return result
directory.list_dir_begin()
var file_name := directory.get_next()
while not file_name.is_empty():
if not directory.current_is_dir() and file_name.get_extension().to_lower() == FACILITY_FILE_EXTENSION:
result.append(directory_path.path_join(file_name))
file_name = directory.get_next()
directory.list_dir_end()
result.sort()
if result.is_empty():
_add_warning(
&"no_definitions_found",
&"",
directory_path,
&"resource_path",
"No facility definitions were found."
)
return result
func _load_and_index(paths: Array[String]) -> void:
var candidates: Array[Dictionary] = []
var seen_paths: Dictionary = {}
for path in paths:
_source_count += 1
if seen_paths.has(path):
_add_warning(
&"duplicate_source_path",
&"",
path,
&"resource_path",
"The same source path was supplied more than once."
)
seen_paths[path] = true
if not _validate_requested_path(path):
continue
var resource := ResourceLoader.load(path)
if resource == null:
_add_error(
&"resource_load_failed",
&"",
path,
&"resource_path",
"The resource could not be loaded."
)
continue
if not resource is FacilityDefinition:
_add_error(
&"wrong_resource_type",
&"",
path,
&"resource",
"Expected FacilityDefinition, got %s." % resource.get_class()
)
continue
var definition := resource as FacilityDefinition
candidates.append({
&"definition": definition,
&"path": path,
&"valid": _validate_facility(definition, path),
})
candidates.sort_custom(_candidate_less)
var counts_by_id: Dictionary = {}
var paths_by_id: Dictionary = {}
for candidate in candidates:
var definition := candidate[&"definition"] as FacilityDefinition
if definition.id.is_empty():
continue
counts_by_id[definition.id] = int(counts_by_id.get(definition.id, 0)) + 1
if not paths_by_id.has(definition.id):
paths_by_id[definition.id] = []
var id_paths := paths_by_id[definition.id] as Array
id_paths.append(String(candidate[&"path"]))
for candidate in candidates:
var definition := candidate[&"definition"] as FacilityDefinition
var path := String(candidate[&"path"])
if definition.id.is_empty():
continue
if int(counts_by_id[definition.id]) > 1:
var duplicate_paths := paths_by_id[definition.id] as Array
_add_error(
&"duplicate_id",
definition.id,
path,
&"id",
"Facility ID is duplicated by: %s." % ", ".join(duplicate_paths)
)
continue
if not bool(candidate[&"valid"]):
continue
_facilities_by_id[definition.id] = definition
_ordered_facility_ids.append(definition.id)
func _validate_requested_path(path: String) -> bool:
var valid := true
if path.is_empty():
_add_error(&"empty_resource_path", &"", path, &"resource_path", "Resource path is empty.")
return false
if not path.begins_with("res://"):
_add_error(
&"non_project_resource_path",
&"",
path,
&"resource_path",
"Facility definitions must use a res:// project path."
)
valid = false
if path.get_extension().to_lower() != FACILITY_FILE_EXTENSION:
_add_error(
&"wrong_resource_extension",
&"",
path,
&"resource_path",
"Facility definitions must use the .tres extension."
)
valid = false
# Exported .tres resources are remapped to binary .res files. Physical
# FileAccess checks incorrectly reject them when no source checkout exists.
if not ResourceLoader.exists(path):
_add_error(
&"resource_not_found",
&"",
path,
&"resource_path",
"Facility definition file does not exist."
)
valid = false
return valid
func _validate_facility(definition: FacilityDefinition, requested_path: String) -> bool:
var valid := true
var facility_id := definition.id
var actual_path := definition.resource_path
if facility_id.is_empty():
_add_error(&"missing_required_value", facility_id, requested_path, &"id", "Facility ID is required.")
valid = false
elif not _is_snake_case_id(String(facility_id)):
_add_error(
&"invalid_id_format",
facility_id,
requested_path,
&"id",
"Facility ID must be lowercase ASCII snake_case and begin with a letter."
)
valid = false
if definition.display_name.strip_edges().is_empty():
_add_error(
&"missing_required_value",
facility_id,
requested_path,
&"display_name",
"Display name is required."
)
valid = false
if definition.description.strip_edges().is_empty():
_add_warning(
&"empty_description",
facility_id,
requested_path,
&"description",
"Description is empty."
)
if actual_path.is_empty():
_add_error(
&"missing_resource_path",
facility_id,
requested_path,
&"resource_path",
"Loaded FacilityDefinition has no resource_path."
)
valid = false
elif actual_path != requested_path:
_add_error(
&"resource_path_mismatch",
facility_id,
requested_path,
&"resource_path",
"Loaded resource_path '%s' does not match its requested path." % actual_path
)
valid = false
if definition.zone < FacilityDefinition.MapZone.ANY \
or definition.zone > FacilityDefinition.MapZone.UNDERGROUND:
_add_error(&"value_out_of_range", facility_id, requested_path, &"zone", "Zone enum value is out of range.")
valid = false
if definition.layer < FacilityDefinition.ViewLayer.FRONT \
or definition.layer > FacilityDefinition.ViewLayer.BACK:
_add_error(&"value_out_of_range", facility_id, requested_path, &"layer", "Layer enum value is out of range.")
valid = false
if definition.footprint.x <= 0 or definition.footprint.y <= 0:
_add_error(
&"value_out_of_range",
facility_id,
requested_path,
&"footprint",
"Footprint dimensions must both be greater than zero."
)
valid = false
if definition.build_minutes <= 0:
_add_error(&"value_out_of_range", facility_id, requested_path, &"build_minutes", "Build time must be greater than zero.")
valid = false
if definition.weight_per_cell not in [1, 2, 3]:
_add_error(&"value_out_of_range", facility_id, requested_path, &"weight_per_cell", "Structural weight must be 1, 2, or 3.")
valid = false
if definition.allowed_rotations.is_empty():
_add_error(&"missing_required_value", facility_id, requested_path, &"allowed_rotations", "At least one rotation is required.")
valid = false
else:
var seen_rotations := {}
for rotation in definition.allowed_rotations:
if rotation not in [0, 90, 180, 270] or seen_rotations.has(rotation):
_add_error(&"invalid_rotation", facility_id, requested_path, &"allowed_rotations", "Rotations must be unique quarter turns.")
valid = false
seen_rotations[rotation] = true
if definition.staff_min < 0 or definition.staff_max < definition.staff_min:
_add_error(&"value_out_of_range", facility_id, requested_path, &"staff_min", "Staff limits are inconsistent.")
valid = false
if definition.capacity < 0 or definition.queue_capacity < 0:
_add_error(&"value_out_of_range", facility_id, requested_path, &"capacity", "Service and queue capacities cannot be negative.")
valid = false
if definition.base_service_minutes <= 0 or definition.base_price < 0:
_add_error(&"value_out_of_range", facility_id, requested_path, &"base_service_minutes", "Service duration must be positive and price cannot be negative.")
valid = false
for item_id: Variant in definition.inventory_inputs:
if StringName(item_id).is_empty() or int(definition.inventory_inputs[item_id]) <= 0:
_add_error(&"invalid_inventory_requirement", facility_id, requested_path, &"inventory_inputs", "Inventory input ids and quantities must be positive.")
valid = false
for item_id: Variant in definition.inventory_outputs:
if StringName(item_id).is_empty() or int(definition.inventory_outputs[item_id]) <= 0:
_add_error(&"invalid_inventory_output", facility_id, requested_path, &"inventory_outputs", "Inventory output ids and quantities must be positive.")
valid = false
if not _validate_construction_cost(definition.construction_cost, facility_id, requested_path):
valid = false
if definition.maintenance_mana < 0:
_add_error(
&"value_out_of_range",
facility_id,
requested_path,
&"maintenance_mana",
"Maintenance mana cannot be negative."
)
valid = false
if definition.staff_min < 0:
_add_error(&"value_out_of_range", facility_id, requested_path, &"staff_min", "Minimum staff cannot be negative.")
valid = false
if definition.staff_max < 0:
_add_error(&"value_out_of_range", facility_id, requested_path, &"staff_max", "Maximum staff cannot be negative.")
valid = false
if definition.staff_max < definition.staff_min:
_add_error(
&"invalid_value_relation",
facility_id,
requested_path,
&"staff_max",
"Maximum staff must be greater than or equal to minimum staff."
)
valid = false
if definition.capacity < 0:
_add_error(&"value_out_of_range", facility_id, requested_path, &"capacity", "Capacity cannot be negative.")
valid = false
if not _validate_tags(definition.tags, facility_id, requested_path):
valid = false
if not _validate_detailed_profile(definition, requested_path):
valid = false
if not facility_id.is_empty() and requested_path.get_file().get_basename() != String(facility_id):
_add_warning(
&"id_filename_mismatch",
facility_id,
requested_path,
&"id",
"Facility ID does not match the resource filename."
)
return valid
func _validate_detailed_profile(definition: FacilityDefinition, path: String) -> bool:
var facility_id := definition.id
if definition.definition_schema_version == FacilityDefinition.LEGACY_SCHEMA_VERSION:
return true
if definition.definition_schema_version != FacilityDefinition.DETAILED_SCHEMA_VERSION:
_add_error(&"unsupported_definition_schema", facility_id, path, &"definition_schema_version", "Facility definition schema is unsupported.")
return false
var valid := true
if definition.category.is_empty() or not _is_snake_case_id(String(definition.category)):
_add_error(&"invalid_category", facility_id, path, &"category", "Detailed facilities require one snake_case category.")
valid = false
if not _validate_service_profiles(definition, path):
valid = false
for field_row: Array in [
[&"service_types", definition.service_types, true],
[&"required_connection_tags", definition.required_connection_tags, false],
[&"preferred_adjacency_tags", definition.preferred_adjacency_tags, false],
[&"forbidden_adjacency_tags", definition.forbidden_adjacency_tags, false],
[&"allowed_guest_tags", definition.allowed_guest_tags, false],
[&"forbidden_guest_tags", definition.forbidden_guest_tags, false],
[&"research_requirements", definition.research_requirements, false],
[&"statistics_keys", definition.statistics_keys, true],
]:
if not _validate_name_list(field_row[1], bool(field_row[2]), facility_id, path, field_row[0]):
valid = false
if not _validate_access_offsets(definition, definition.guest_access_offsets, &"guest_access_offsets", path):
valid = false
if not _validate_access_offsets(definition, definition.staff_access_offsets, &"staff_access_offsets", path):
valid = false
if not definition.pass_through and definition.guest_access_offsets.is_empty() and definition.staff_access_offsets.is_empty():
_add_error(&"missing_access_point", facility_id, path, &"guest_access_offsets", "A non-route detailed facility requires a guest or staff access point.")
valid = false
if definition.max_level < 1 or definition.max_level > 5 or definition.upgrade_costs.size() != definition.max_level - 1:
_add_error(&"invalid_upgrade_ladder", facility_id, path, &"upgrade_costs", "Upgrade costs must contain exactly max_level - 1 entries.")
valid = false
for index: int in definition.upgrade_costs.size():
if not _validate_construction_cost(definition.upgrade_costs[index], facility_id, path):
valid = false
if definition.demolition_refund_percent < 0 or definition.demolition_refund_percent > 100:
_add_error(&"value_out_of_range", facility_id, path, &"demolition_refund_percent", "Demolition refund percent must be from 0 through 100.")
valid = false
if definition.unlock_rank < 1 or definition.unlock_rank > 7:
_add_error(&"value_out_of_range", facility_id, path, &"unlock_rank", "Unlock rank must be from 1 through 7.")
valid = false
var expected_schedule_keys: Array[StringName] = [&"morning", &"day", &"night"]
if definition.opening_schedule.size() != expected_schedule_keys.size() \
or not definition.opening_schedule.has(&"morning") \
or not definition.opening_schedule.has(&"day") \
or not definition.opening_schedule.has(&"night"):
_add_error(&"invalid_opening_schedule", facility_id, path, &"opening_schedule", "Opening schedule must contain exactly morning, day, and night.")
valid = false
else:
for time_band: StringName in expected_schedule_keys:
if not definition.opening_schedule[time_band] is bool:
_add_error(&"wrong_value_type", facility_id, path, &"opening_schedule", "Opening schedule values must be booleans.")
valid = false
if not _validate_nonnegative_int_map(definition.incident_weights, facility_id, path, &"incident_weights"):
valid = false
if not _validate_policy_schema(definition.policy_schema, facility_id, path):
valid = false
if definition.adult_content and (not definition.tags.has(&"adult_only") or definition.content_replacement_id.is_empty()):
_add_error(&"unsafe_adult_content_profile", facility_id, path, &"adult_content", "Adult content requires adult_only and a safe content replacement ID.")
valid = false
if not definition.adult_content and not definition.content_replacement_id.is_empty():
_add_warning(&"unused_content_replacement", facility_id, path, &"content_replacement_id", "A non-adult facility does not use a content replacement.")
return valid
func _validate_service_profiles(definition: FacilityDefinition, path: String) -> bool:
var valid := true
if definition.default_service_type.is_empty() or not definition.service_types.has(definition.default_service_type):
_add_error(&"invalid_default_service", definition.id, path, &"default_service_type", "Default service must reference one declared service type.")
valid = false
if definition.service_profiles.size() != definition.service_types.size():
_add_error(&"service_profile_mismatch", definition.id, path, &"service_profiles", "Every declared service type requires exactly one profile.")
valid = false
for service_type: StringName in definition.service_types:
if not definition.service_profiles.has(service_type) or not definition.service_profiles[service_type] is Dictionary:
_add_error(&"missing_service_profile", definition.id, path, &"service_profiles", "Service '%s' requires a Dictionary profile." % service_type)
valid = false
continue
var profile := definition.service_profiles[service_type] as Dictionary
for required_key: StringName in [&"duration_minutes", &"base_quality", &"base_price", &"capacity_units", &"need_effects"]:
if not profile.has(required_key):
_add_error(&"missing_service_profile_field", definition.id, path, &"service_profiles", "Service '%s' is missing '%s'." % [service_type, required_key])
valid = false
if profile.has(&"duration_minutes") and (not profile[&"duration_minutes"] is int or int(profile[&"duration_minutes"]) <= 0):
_add_error(&"invalid_service_profile_value", definition.id, path, &"service_profiles", "Service '%s' duration_minutes must be a positive integer." % service_type)
valid = false
if profile.has(&"base_quality") and (not profile[&"base_quality"] is int or int(profile[&"base_quality"]) < 0 or int(profile[&"base_quality"]) > 100):
_add_error(&"invalid_service_profile_value", definition.id, path, &"service_profiles", "Service '%s' base_quality must be an integer from 0 through 100." % service_type)
valid = false
if profile.has(&"base_price") and (not profile[&"base_price"] is int or int(profile[&"base_price"]) < 0):
_add_error(&"invalid_service_profile_value", definition.id, path, &"service_profiles", "Service '%s' base_price must be a non-negative integer." % service_type)
valid = false
if profile.has(&"capacity_units") and (not profile[&"capacity_units"] is int or int(profile[&"capacity_units"]) <= 0):
_add_error(&"invalid_service_profile_value", definition.id, path, &"service_profiles", "Service '%s' capacity_units must be a positive integer." % service_type)
valid = false
if not profile.has(&"need_effects"):
pass # The missing-field issue was emitted above.
elif not profile[&"need_effects"] is Dictionary:
_add_error(&"invalid_service_profile_value", definition.id, path, &"service_profiles", "Service '%s' need_effects must be a Dictionary." % service_type)
valid = false
elif not _validate_signed_int_map(profile[&"need_effects"], -100, 100, definition.id, path, &"service_profiles"):
valid = false
return valid
func _validate_name_list(values: Array[StringName], required: bool, facility_id: StringName, path: String, field: StringName) -> bool:
if required and values.is_empty():
_add_error(&"missing_required_value", facility_id, path, field, "%s requires at least one ID." % field)
return false
var valid := true
var seen: Dictionary = {}
for value: StringName in values:
if value.is_empty() or not _is_snake_case_id(String(value)) or seen.has(value):
_add_error(&"invalid_id_list", facility_id, path, field, "%s must contain unique snake_case IDs." % field)
valid = false
seen[value] = true
return valid
func _validate_access_offsets(definition: FacilityDefinition, offsets: Array[Vector2i], field: StringName, path: String) -> bool:
var valid := true
var seen: Dictionary = {}
for offset: Vector2i in offsets:
if offset.x < 0 or offset.y < 0 or offset.x >= definition.footprint.x or offset.y >= definition.footprint.y or seen.has(offset):
_add_error(&"invalid_access_offset", definition.id, path, field, "Access points must be unique cells inside the unrotated footprint.")
valid = false
seen[offset] = true
return valid
func _validate_nonnegative_int_map(values: Dictionary, facility_id: StringName, path: String, field: StringName) -> bool:
var valid := true
for raw_key: Variant in values:
var key := StringName(raw_key)
if key.is_empty() or not _is_snake_case_id(String(key)) or not values[raw_key] is int or int(values[raw_key]) < 0:
_add_error(&"invalid_weight_map", facility_id, path, field, "%s requires snake_case IDs and non-negative integer weights." % field)
valid = false
return valid
func _validate_signed_int_map(values: Dictionary, minimum: int, maximum: int, facility_id: StringName, path: String, field: StringName) -> bool:
var valid := true
for raw_key: Variant in values:
var key := StringName(raw_key)
if key.is_empty() or not _is_snake_case_id(String(key)) or not values[raw_key] is int or int(values[raw_key]) < minimum or int(values[raw_key]) > maximum:
_add_error(&"invalid_signed_value_map", facility_id, path, field, "%s requires snake_case IDs and integer values from %d through %d." % [field, minimum, maximum])
valid = false
return valid
func _validate_policy_schema(values: Dictionary, facility_id: StringName, path: String) -> bool:
var valid := true
for raw_key: Variant in values:
var key := StringName(raw_key)
var row: Variant = values[raw_key]
if key.is_empty() or not _is_snake_case_id(String(key)) or not row is Dictionary:
_add_error(&"invalid_policy_schema", facility_id, path, &"policy_schema", "Policy entries require snake_case IDs and Dictionary schemas.")
valid = false
continue
var schema := row as Dictionary
if not schema.has(&"type") or not schema.has(&"default") or StringName(schema[&"type"]) not in [&"bool", &"enum", &"int"]:
_add_error(&"invalid_policy_schema", facility_id, path, &"policy_schema", "Every policy requires type and default; type must be bool, enum, or int.")
valid = false
return valid
func _validate_construction_cost(cost: Dictionary, facility_id: StringName, path: String) -> bool:
var valid := true
for required_key in [&"gold", &"materials"]:
if not cost.has(required_key):
_add_error(
&"missing_required_key",
facility_id,
path,
&"construction_cost",
"Construction cost is missing '%s'." % required_key
)
valid = false
continue
var value: Variant = cost[required_key]
if not value is int:
_add_error(
&"wrong_value_type",
facility_id,
path,
&"construction_cost",
"Construction cost '%s' must be an integer." % required_key
)
valid = false
elif int(value) < 0:
_add_error(
&"value_out_of_range",
facility_id,
path,
&"construction_cost",
"Construction cost '%s' cannot be negative." % required_key
)
valid = false
var cost_keys := cost.keys()
cost_keys.sort()
for key in cost_keys:
if key != &"gold" and key != &"materials":
_add_warning(
&"unknown_cost_key",
facility_id,
path,
&"construction_cost",
"Unknown construction cost key '%s' is ignored by the base game." % key
)
return valid
func _validate_tags(tags: Array[StringName], facility_id: StringName, path: String) -> bool:
var valid := true
var seen: Dictionary = {}
for tag in tags:
if tag.is_empty() or not _is_snake_case_id(String(tag)):
_add_error(
&"invalid_tag_format",
facility_id,
path,
&"tags",
"Tags must be non-empty lowercase ASCII snake_case identifiers."
)
valid = false
if seen.has(tag):
_add_warning(&"duplicate_tag", facility_id, path, &"tags", "Duplicate tag '%s'." % tag)
seen[tag] = true
return valid
func _is_snake_case_id(value: String) -> bool:
if value.is_empty():
return false
var first := value.unicode_at(0)
if first < 97 or first > 122:
return false
var previous_was_underscore := false
for index in value.length():
var character := value.unicode_at(index)
var is_letter := character >= 97 and character <= 122
var is_number := character >= 48 and character <= 57
var is_underscore := character == 95
if not is_letter and not is_number and not is_underscore:
return false
if is_underscore and previous_was_underscore:
return false
previous_was_underscore = is_underscore
return not previous_was_underscore
func _finish_rebuild() -> void:
_ordered_facility_ids.sort_custom(_id_less)
_errors.sort_custom(_issue_less)
_warnings.sort_custom(_issue_less)
func _add_error(
code: StringName,
definition_id: StringName,
resource_path: String,
field: StringName,
message: String
) -> void:
_errors.append(_make_issue(SEVERITY_ERROR, code, definition_id, resource_path, field, message))
func _add_warning(
code: StringName,
definition_id: StringName,
resource_path: String,
field: StringName,
message: String
) -> void:
_warnings.append(_make_issue(SEVERITY_WARNING, code, definition_id, resource_path, field, message))
func _make_issue(
severity: StringName,
code: StringName,
definition_id: StringName,
resource_path: String,
field: StringName,
message: String
) -> Dictionary:
return {
&"severity": severity,
&"code": code,
&"definition_id": definition_id,
&"resource_path": resource_path,
&"field": field,
&"message": message,
}
func _candidate_less(left: Dictionary, right: Dictionary) -> bool:
var left_definition := left[&"definition"] as FacilityDefinition
var right_definition := right[&"definition"] as FacilityDefinition
var left_id := String(left_definition.id)
var right_id := String(right_definition.id)
if left_id != right_id:
return left_id < right_id
return String(left[&"path"]) < String(right[&"path"])
func _id_less(left: StringName, right: StringName) -> bool:
return String(left) < String(right)
func _issue_less(left: Dictionary, right: Dictionary) -> bool:
var left_id := String(left[&"definition_id"])
var right_id := String(right[&"definition_id"])
if left_id != right_id:
return left_id < right_id
var left_path := String(left[&"resource_path"])
var right_path := String(right[&"resource_path"])
if left_path != right_path:
return left_path < right_path
var left_field := String(left[&"field"])
var right_field := String(right[&"field"])
if left_field != right_field:
return left_field < right_field
var left_code := String(left[&"code"])
var right_code := String(right[&"code"])
if left_code != right_code:
return left_code < right_code
return String(left[&"message"]) < String(right[&"message"])