# Target-following effects use actor-local offset. Snapshot effects use the # fly-target's world position and a world-aligned offset (40250 type 10). extends Node const CONVERSION := Basis(Vector3(0.01, 0, 0), Vector3(0, 0, -0.01), Vector3(0, 0.01, 0)) var target: WeakRef var offset := Vector3.ZERO func configure(node: Node3D, source_offset: Vector3, following: bool) -> bool: if not is_instance_valid(node) or not node.is_inside_tree(): return false target = weakref(node) offset = CONVERSION * source_offset process_priority = 100 set_process(following) if following: _process(0.0) else: get_parent().global_position = fly_target_position(node) + offset return true static func fly_target_position(node: Node3D) -> Vector3: # The reference model-index 0 sphere is built from bone OBBs, not the # union of visible attachments or the rendered vertex AABB. var models := node.find_children("*", "Metin2Model", true, false) if node.has_method("get_fly_target_bounds"): models.push_front(node) for model in models: var result: Dictionary = model.get_fly_target_bounds() if result.get("valid", false): var body_bounds: AABB = result.bounds return model.to_global(body_bounds.get_center()) # The reference returns the model bounding sphere centre, not its feet. var meshes := node.find_children("*", "MeshInstance3D", true, false) var bounds := AABB() var found := false for mesh in meshes: if mesh.mesh == null or not mesh.is_visible_in_tree(): continue var box: AABB = mesh.global_transform * mesh.get_aabb() bounds = bounds.merge(box) if found else box found = true return bounds.get_center() if found else node.global_position func _process(_delta: float) -> void: var node: Node3D = target.get_ref() if target else null if not is_instance_valid(node) or not node.is_inside_tree(): get_parent().queue_free() return get_parent().global_transform = node.global_transform * Transform3D(Basis.IDENTITY, offset)