class_name DiveSite extends Node3D const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd") const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd") const SitePlacement = preload("res://game/domain/sites/site_placement.gd") const BathymetricTerrainActor = preload( "res://game/world/sites/bathymetric_terrain_actor.gd" ) const SeabedFloraActor = preload("res://game/world/sites/seabed_flora_actor.gd") const METAL_REFLECTIVITY := 1.0 const BEACON_COLOR := Color(0.2, 0.78, 0.9) @onready var _terrain: BathymetricTerrainActor = %Terrain @onready var _flora: SeabedFloraActor = %Flora @onready var _placements_root: Node3D = %Placements var _definition: DiveSiteDefinition var _placeholder_count := 0 func configure(definition: DiveSiteDefinition) -> String: if _terrain == null or _placements_root == null: return "dive site is not in the scene tree" if definition == null: return "dive site definition is missing" var field := definition.height_field() if field == null: return "dive site definition is invalid" var terrain_error := _terrain.configure(field) if not terrain_error.is_empty(): return terrain_error if _flora != null: # The flora is seeded by the site id, so a site always grows the same meadows. var flora_error := _flora.configure(field, hash(String(definition.site_id))) if not flora_error.is_empty(): return flora_error _definition = definition for child in _placements_root.get_children(): _placements_root.remove_child(child) child.free() _placeholder_count = 0 for placement: SitePlacement in definition.placements: _place(placement) return "" func definition() -> DiveSiteDefinition: return _definition func terrain() -> BathymetricTerrainActor: return _terrain func flora() -> SeabedFloraActor: return _flora func spawn_transform() -> Transform3D: return _definition.spawn_transform() if _definition != null else Transform3D.IDENTITY func placeholder_count() -> int: return _placeholder_count func _place(placement: SitePlacement) -> void: if placement.is_sonar_metal(): _placements_root.add_child(_metal_placeholder(placement)) _placeholder_count += 1 elif placement.kind == SitePlacement.Kind.VESSEL_ANCHOR: _placements_root.add_child(_beacon(placement)) _placeholder_count += 1 func _metal_placeholder(placement: SitePlacement) -> StaticBody3D: var body := StaticBody3D.new() body.name = String(placement.placement_id) body.position = placement.position_m body.rotation.y = placement.yaw_radians body.set_meta("sonar_material", SonarMaterial.Value.METAL) body.set_meta("sonar_reflectivity", METAL_REFLECTIVITY) var mesh := BoxMesh.new() mesh.size = placement.footprint_m var mesh_instance := MeshInstance3D.new() mesh_instance.name = "Mesh" mesh_instance.mesh = mesh var material := StandardMaterial3D.new() material.albedo_color = Color(0.24, 0.18, 0.08) material.metallic = 0.88 material.roughness = 0.27 mesh_instance.material_override = material body.add_child(mesh_instance) var shape := BoxShape3D.new() shape.size = placement.footprint_m var collision := CollisionShape3D.new() collision.name = "Collision" collision.shape = shape body.add_child(collision) return body func _beacon(placement: SitePlacement) -> MeshInstance3D: var mesh := SphereMesh.new() mesh.radius = 0.55 mesh.height = 1.1 var beacon := MeshInstance3D.new() beacon.name = String(placement.placement_id) beacon.mesh = mesh beacon.position = placement.position_m var material := StandardMaterial3D.new() material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED material.albedo_color = BEACON_COLOR material.emission_enabled = true material.emission = BEACON_COLOR material.emission_energy_multiplier = 5.0 beacon.material_override = material var light := OmniLight3D.new() light.name = "BeaconLight" light.omni_range = 12.0 light.light_color = BEACON_COLOR light.light_energy = 4.0 beacon.add_child(light) return beacon