class_name SpinGlideAbility extends RunnerAbility const PUNT_ACCELERATION: float = 30.0 const DROP_KICK_FACTOR: float = 3.0 const KNOCKBACK_ATTENUATION: float = 0.125 @export var animation_in_water: StringName @export var timeout: float = 1.0 @export var drop_factor: float = 1.0 var saved_gravity_scale: float var ending: bool var timer: float var in_water: bool func begin(runner: Runner) -> void: saved_gravity_scale = runner.gravity_scale runner.gravity_scale = 0.01 runner.linear_velocity = runner.linear_velocity.slide(runner.up_normal) ending = false timer = timeout in_water = false func integrate_forces( runner: Runner, body_state: PhysicsDirectBodyState3D ) -> void: if ( in_water or body_state.total_gravity.dot(Vector3.UP) > 0.0 ) and not ending: in_water = true if runner.animation_player.current_animation != animation_in_water: runner.animation_player.play(animation_in_water) body_state.linear_velocity += drop_factor*runner.basis.z/2.0 body_state.linear_velocity -= ( runner.up_normal * (runner.impetus*runner.basis).z * runner.run_acceleration * body_state.step ) timer -= body_state.step if timer <= 0.0 or ( body_state.total_gravity.dot(Vector3.UP) < 0.0 ): on_repeated_input(runner) elif not runner.wall_normal.is_zero_approx(): runner.change_state(&'wall_slide') elif ending: if not runner.floor_normal.is_zero_approx(): runner.audio_player.stream = runner.land_sound runner.audio_player.play() runner.change_state(&'run') else: timer -= body_state.step if timer <= 0.0: on_repeated_input(runner) for i in body_state.get_contact_count(): var other := body_state.get_contact_collider_object(i) if other is Runner: var rother := other as Runner rother.knockback( runner.global_position, runner.mass*PUNT_ACCELERATION*( DROP_KICK_FACTOR if ending else 1.0 )*KNOCKBACK_ATTENUATION ) elif other is RigidBody3D: var rother := other as RigidBody3D rother.apply_force( runner.mass*( runner.impetus + runner.up_normal ).normalized()*PUNT_ACCELERATION*( DROP_KICK_FACTOR if ending else 1.0 ), body_state.get_contact_collider_position(i) - rother.global_position ) func orientation(runner: Runner) -> Vector3: if in_water: return runner.linear_velocity else: return runner.basis.z func animation_speed(runner: Runner) -> float: return 1.0 + runner.linear_velocity.length()/20.0 func end(runner: Runner) -> void: runner.gravity_scale = saved_gravity_scale func on_repeated_input(runner: Runner) -> void: if in_water: runner.change_state(&'swim') elif not ending: runner.audio_player.stream = runner.jump_sound runner.audio_player.play() runner.gravity_scale = saved_gravity_scale runner.linear_velocity -= ( drop_factor*runner.top_run_speed*runner.up_normal ) ending = true