class_name Socket extends Node3D @export var snapPoint: Marker3D @export var snapArea: Area3D @export var snapDistance: float = 0.5 @export var lockOnSnap: bool = true @export var hologramMaterial: Material var hologramInstance: MeshInstance3D var candidate: Pickupable = null var attachedObject: Pickupable = null func _ready() -> void: if snapArea: snapArea.body_entered.connect(onBodyEntered) snapArea.body_exited.connect(onBodyExited) hologramInstance = MeshInstance3D.new() hologramInstance.top_level = true hologramInstance.visible = false if hologramMaterial: hologramInstance.material_override = hologramMaterial add_child(hologramInstance) func canAttach(object: Pickupable) -> bool: if attachedObject != null: return false if object == null or object.snapAnchor == null: return false var distance := object.snapAnchor.global_position.distance_to(snapPoint.global_position) return distance <= snapDistance func attachObject(object: Pickupable) -> bool: if not canAttach(object): return false var targetTransform := getSnapTransform(object) var body := object.body body.linear_velocity = Vector3.ZERO body.angular_velocity = Vector3.ZERO body.global_transform = targetTransform if lockOnSnap: body.freeze = true attachedObject = object candidate = null object.currentSocket = self if hologramInstance: hologramInstance.visible = false return true func detachObject() -> void: if attachedObject == null: return var body := attachedObject.body if lockOnSnap: body.freeze = false attachedObject.currentSocket = null attachedObject = null func getSnapTransform(object: Pickupable) -> Transform3D: var objectTransform := object.body.global_transform var anchorTransform := object.snapAnchor.global_transform var anchorOffset := objectTransform.affine_inverse() * anchorTransform return snapPoint.global_transform * anchorOffset.affine_inverse() func onBodyEntered(body: Node3D): if attachedObject != null: return if body is RigidBody3D and body.get_parent() is Pickupable: var pickup = body.get_parent() as Pickupable pickup.currentSocket = self candidate = pickup if pickup.visualMeshNode and pickup.visualMeshNode.mesh: hologramInstance.mesh = pickup.visualMeshNode.mesh var targetBodyTransform = getSnapTransform(pickup) var meshLocalTranform = pickup.body.global_transform.affine_inverse() * pickup.visualMeshNode.global_transform hologramInstance.global_transform = targetBodyTransform * meshLocalTranform hologramInstance.visible = true func onBodyExited(body: Node3D): if body is RigidBody3D and body.get_parent() is Pickupable: var pickup = body.get_parent() as Pickupable if pickup.currentSocket == self and attachedObject != pickup: pickup.currentSocket = null if candidate == pickup: candidate = null if hologramInstance: hologramInstance.visible = false