rusteal 0.8.0

Rust bindings for Unreal Engine 5.8.2
use bindings::engine::{
    AnimInstance, AnimInstanceExt, Character, CharacterExt, CharacterMovementComponent,
    CharacterMovementComponentExt, ControllerExt, GameplayStatics, MovementComponentExt,
    NavMovementComponentExt,
};
use bindings::prelude::*;
use glam::DVec3;
use rusteal_runtime::runtime::{OwnedStruct, UObjectRef};
use rusteal_runtime::{uclass, uclass_impl};

const MOVING_SPEED: f64 = 0.01;
const DIRECTION_LIMIT: f64 = 45.0;

#[uclass(parent = AnimInstance)]
pub struct CopycatAnimInstance {
    #[uproperty(EditAnywhere, BlueprintReadWrite, category = "Copycat")]
    source_character: UObjectRef<Character>,

    #[uproperty(BlueprintReadOnly, category = "Copycat")]
    character: UObjectRef<Character>,

    #[uproperty(BlueprintReadOnly, category = "Copycat")]
    movement_component: UObjectRef<CharacterMovementComponent>,

    #[uproperty(BlueprintReadOnly, category = "Copycat")]
    velocity: OwnedStruct<FVector>,

    #[uproperty(BlueprintReadOnly, category = "Copycat", default = 0.0)]
    ground_speed: f64,

    #[uproperty(BlueprintReadOnly, category = "Copycat", default = 0.0)]
    direction: f64,

    #[uproperty(BlueprintReadOnly, category = "Copycat", default = false)]
    should_move: bool,

    #[uproperty(BlueprintReadOnly, category = "Copycat", default = false)]
    is_falling: bool,
}

#[uclass_impl]
impl CopycatAnimInstance {
    #[ufunction(Override)]
    fn blueprint_update_animation(&mut self, _delta_time_x: f32) {
        if !self.character().is_valid() || !self.movement_component().is_valid() {
            self.find_character();
        }

        let (Ok(anim), Ok(character), Ok(movement)) = (
            self.as_ref().checked(),
            self.character().checked(),
            self.movement_component().checked(),
        ) else {
            return;
        };

        let velocity = movement.get_velocity();
        let ground_speed = ground_speed(velocity.to_dvec3());
        let direction =
            f64::from(anim.calculate_direction(&velocity, &character.k2_get_actor_rotation()));

        self.set_velocity(&velocity);
        self.set_ground_speed(ground_speed);
        self.set_direction(clamp_direction(
            direction,
            movement.get_orient_rotation_to_movement(),
        ));
        self.set_should_move(should_move(
            ground_speed,
            movement.get_current_acceleration().to_dvec3(),
        ));
        self.set_is_falling(movement.is_falling());
    }
}

impl CopycatAnimInstance {
    fn find_character(&mut self) {
        let character = if self.source_character().is_valid() {
            self.source_character()
        } else {
            let controller = GameplayStatics::get_player_controller(self.as_ref().upcast_to(), 0);

            controller
                .checked()
                .ok()
                .and_then(|c| c.k2_get_pawn().cast::<Character>().ok())
                .unwrap_or_default()
        };

        let movement = character
            .checked()
            .map(|c| c.get_character_movement())
            .unwrap_or_default();

        self.set_character(character);
        self.set_movement_component(movement);
    }
}

pub fn ground_speed(velocity: DVec3) -> f64 {
    velocity.truncate().length()
}

pub fn should_move(ground_speed: f64, acceleration: DVec3) -> bool {
    ground_speed > MOVING_SPEED && acceleration != DVec3::ZERO
}

pub fn clamp_direction(direction: f64, orient_to_movement: bool) -> f64 {
    if orient_to_movement {
        direction.clamp(-DIRECTION_LIMIT, DIRECTION_LIMIT)
    } else {
        direction
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ground_speed_ignores_the_vertical_axis() {
        assert_eq!(ground_speed(DVec3::new(3.0, 4.0, -900.0)), 5.0);
    }

    #[test]
    fn moves_only_with_speed_and_acceleration() {
        assert!(should_move(300.0, DVec3::X));
        assert!(!should_move(300.0, DVec3::ZERO));
        assert!(!should_move(0.005, DVec3::X));
    }

    #[test]
    fn clamps_the_direction_when_orienting_to_movement() {
        assert_eq!(clamp_direction(170.0, true), 45.0);
        assert_eq!(clamp_direction(-170.0, true), -45.0);
        assert_eq!(clamp_direction(170.0, false), 170.0);
    }
}