extends EnemyHurtbox # The distance from one end to the other while standing @export var broadth = 250 var move_dir = [-1, 1].pick_random() const angular_speed = 0.25 # The angle from the base to the moving end var angle = 0.0 if move_dir == 1 else PI @onready var segments : Array[Node] = $Segments.get_children() @onready var segment_count = segments.size() var dead = false func _process(delta: float) -> void: if dead: return # Fall slowly while not grounded if not $GroundSensor.has_overlapping_bodies(): position += 200 * delta * $EarthAligner.down var y = position.length() var ratio = - move_dir * broadth / (2 * y) # Due to the curvature of the ground, the leech can not just prescribe a semicircle. # 2 * rot_angle determines how much further than 180° the moving leg has to move. # This agrees with the angle of the arc spanned by the leeches standing ends. var rot_angle = - 2 * asin(ratio) angle -= TAU * delta * angular_speed * move_dir # Once the rotation has finished, the other leg is used as the leeches center. # The leeches position has to be updated accordingly. if(angle > PI + abs(rot_angle) / 2 or angle < - abs(rot_angle) / 2): angle = fmod(angle + PI, PI) position = position.rotated(rot_angle) # StepChecker determines whether there is ground for the next step. # Place the StepChecker on the side of the next step to be made. if(move_dir == 1 and angle < 1 or move_dir == -1 and angle > PI - 1): $StepChecker.global_position = position.rotated(rot_angle) $StepChecker.rotation = rot_angle # At some point of the movement, check whether there is ground to step on, turn around otherwise. if(move_dir == 1 and angle < 0.5 or move_dir == -1 and angle > PI - 0.5): if(not $StepChecker.has_overlapping_bodies()): move_dir *= -1 # Update the position and rotation according to the end's position for i in range(segment_count): var segment_pos_data = calculate_segment_location_and_rotation(i) if not is_instance_valid(segments[i]): get_tree().get_root().print_tree_pretty() segments[i].position = segment_pos_data.position segments[i].rotation = segment_pos_data.rotation func calculate_segment_location_and_rotation (i) -> Dictionary: var aerial_end_location = Vector2.from_angle(-angle) * broadth # Compute the gravicenter of the standing end, the moving end and the apex of the movement. var gravicenter = (aerial_end_location + Vector2(0, -broadth) + Vector2.ZERO) / 3 # Compute the circle through the above gravicenter, the standing end and the moving end. var ax = gravicenter.x var ay = gravicenter.y var bx = aerial_end_location.x var by = aerial_end_location.y var cx = 0 var cy = 0 var d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) var ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d var uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d var circum_center = Vector2(ux, uy) var radius = circum_center.length() # Determine the direction of the correct arc between the standing and the moving end var switch_arc_dir = false # When the moving end crosses above the standing end, the circumcenter jumps # from one side of the leech to the other, reverting the direction of the arc if ux < 0: switch_arc_dir = !switch_arc_dir # For sufficiently large size of circum_center.angle() it can happen that # the sign of the (circum_center - aerial_end_location).angle() flips while # that of circum_center.angle() doesn't, which has to be counteracted. if circum_center.angle() > 1: switch_arc_dir = !switch_arc_dir var angle1 = - PI + circum_center.angle() var angle2 = - PI + (circum_center - aerial_end_location).angle() if(switch_arc_dir): angle1 += TAU # In the edge case where the leech is almost a straight line, the circum_center # and radius approach infty, leading to numerical errors producing flickering. # This is ruled out by treating these cases as actual straight lines. if radius > 1000000: return {"position" : Vector2.UP * broadth * i / (segment_count - 1), "rotation" : 3 * PI / 2} # Otherwise, the segments are distributed regularly along the arc. else: var arc_angle = (i * angle1 + (segment_count - 1 - i) * angle2)/(segment_count - 1) return {"position": circum_center + radius * Vector2.from_angle(arc_angle), "rotation": arc_angle + sign(ux) * PI/2} func _on_damage_taken(_damage, _dir, _id): $AudioStreamPlayer2D.play() func _on_death(): # Free all other children while waiting for the death sound to play. dead = true for child in get_children(): if not child is AudioStreamPlayer2D: child.queue_free() $AudioStreamPlayer2D.play() await $AudioStreamPlayer2D.finished queue_free()