aboutsummaryrefslogblamecommitdiffstats
path: root/rust/src/basic_die.rs
blob: 2a61197b91ec8f64c057b4e006e8e0dff287f362 (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                  


                                       


                                    
                   

                                        
                                           




                                          

                                                  

                                                          
                                                      
                               

                                               


                            
                        


                            





                                           








                                                                   
                                      
                  


                                                     
                                   
                                  
                                                          
                                   


                                             
                              


                                                            


                                   

                                             
                              



             
                                                  
 
                                                                                   

                                        

                                                                     












                                                                                             
                 

             
 













                                                                                                                         


             
                                                                          















                                                                           
 
                                         


                                                                                    

                                                                                                   
                                                             

                                                       
                                                                                                     
             



             
                                                                           




                                                             
                                                               







                                                                              

                                                                                                     






                                                                                 









                                                                                              





                                                                                  






                                                                                                     

                                                                                                     









                                                                                  
                                                                 

                                             
                                

                                                                                               
                         
                                     


                   

                                                                                                     

                                   
                                                                               


                                                                       
                          











                                                                      

                                                                                                     



























                                                                                            












                                                                                                                
 

                                
                                               

                                                                    
                          
                                                                                                               
                                                                                                      


                                                 


                                                                
 











                                                                           

                                                                     
     
 
use std::borrow::Borrow;
use gdnative::api::*;
use gdnative::prelude::*;

/// the input state for the player
enum InputState {
    Default,
    Shooting,
    Moving
}
    

type SpatialRef = Option<Ref<Spatial>>;

/// the basic die used by the player
#[derive(NativeClass)]
#[inherit(Spatial)]
#[register_with(Self::register_builder)]
pub struct BasicDie {
    #[property(path="camera/camera_clamp")]
    camera_clamp: Vector2,
    #[property(path="shooting/max_force")]
    max_force: f32,
    #[property(path="shooting/up_angle")]
    up_angle: f32,
    #[property(path="shooting/stopping_velocity")]
    stopping_velocity: f32,
    #[property(path="shooting/stopping_min_milliseconds")]
    stopping_min_ms: i64,
    #[property(path="input/camera_mouse_sensitivity")]
    mouse_sensitivity: Vector2,
    #[property(path="input/shoot_sensitivity")]
    shoot_sensitivity: f32,

    input_state: InputState,
    current_force: f32,
    last_shot_time: i64,

    action_shooting: String,

    node_die: SpatialRef,

    node_camera_root: SpatialRef,
    node_camera_arm_horizontal: SpatialRef,
    node_camera_arm_vertical: SpatialRef,
    node_camera: SpatialRef,
}

#[methods]
impl BasicDie {
    // Register the builder for methods, properties and/or signals.
    fn register_builder(_builder: &ClassBuilder<Self>) {
        godot_print!("BasicDie builder is registered!");
    }

    fn new(_owner: &Spatial) -> Self {
        BasicDie {
            camera_clamp: Vector2 { x: 0.0, y: 0.0 },
            max_force: 0.0,
            up_angle: 5.0,
            stopping_velocity: 0.0,
            stopping_min_ms: 1000,
            mouse_sensitivity: Vector2 { x: 1.0, y: 1.0 },
            shoot_sensitivity: 1.0,

            input_state: InputState::Default,
            current_force: 0.0,
            last_shot_time: 0,

            action_shooting: String::from("mouse_btn_left"),

            node_die: None,

            node_camera_root: None,
            node_camera_arm_horizontal: None,
            node_camera_arm_vertical: None,
            node_camera: None,
        }
    }

    #[export]
    unsafe fn _ready(&mut self, owner: &Spatial) {

        Input::godot_singleton().set_mouse_mode(Input::MOUSE_MODE_CAPTURED);       
        owner.set_physics_process(true);

        self.last_shot_time = OS::godot_singleton().get_ticks_msec();

        let search_node = | parent: &SpatialRef, name: String | {
            let parent_node = match parent {
                Some(node) => node.assume_safe().as_ref(),
                None => owner
            };
            match parent_node.get_node(NodePath::from_str(&name)) {
                None => { godot_warn!("Could not find {}.", name); None },
                Some(node) => {
                    let save_node = node.assume_safe();
                    match save_node.cast::<Spatial>() {
                        None => { godot_warn!("{} was not of type 'Spatial'.", name); None },
                        Some(casted) => Some(casted.claim())
                    }
                }
            }
        };

        // look for the camera and arm nodes
        self.node_camera_root = search_node(&None, String::from("Camera"));
        self.node_camera_arm_horizontal = search_node(&self.node_camera_root, String::from("CameraArmHorizontal"));
        self.node_camera_arm_vertical = search_node(&self.node_camera_arm_horizontal, String::from("CameraArmVertical"));
        self.node_camera = search_node(&self.node_camera_arm_vertical, String::from("Camera"));

        godot_print!("{:?}", self.node_camera_root);
        godot_print!("{:?}", self.node_camera_arm_horizontal);
        godot_print!("{:?}", self.node_camera_arm_vertical);
        godot_print!("{:?}", self.node_camera);

        self.node_die = search_node(&None, String::from("W8"));

        godot_print!("Player is ready");
    }

    #[export]
    unsafe fn _physics_process(&mut self, _owner: &Spatial, _delta: f64) {
        
        let die = match self.node_die {
            None => { godot_warn!("No W8 assigned."); return; },
            Some(node) => match node.assume_safe().cast::<RigidBody>() {
                None => { godot_warn!("W8 is not a RigidBody."); return; },
                Some(rb) => rb
            }
        };

        // let the cam follow the die
        match self.node_camera_root {
            None => { godot_warn!("No W8 assigned."); },
            Some(cam) => {
                cam.assume_safe().set_translation(die.translation());
            }
        }

        // detect if the die stops moving
        let delta_ms = OS::godot_singleton().get_ticks_msec() - self.last_shot_time;
        if matches!(self.input_state, InputState::Moving) 
           && (delta_ms > self.stopping_min_ms) {

            // check if the velocity is less than the threshold and change input state in that case
            let current_vel = die.linear_velocity().length();
            if current_vel <= self.stopping_velocity {
                self.input_state = InputState::Default;
                godot_print!("Die stopped moving at velocity {} after {} ms", current_vel, delta_ms);
            }
        };
    }

    #[export]
    unsafe fn _input(&mut self, _owner: &Spatial, event: Ref<InputEvent>) {

        self.general_input(event.borrow());

        match self.input_state {
            InputState::Default => self.default_input(event),
            InputState::Shooting => self.shooting_input(event),
            InputState::Moving => self.moving_input(event),
        }
    }

    /// this input method will always be called, regardless of the input state
    unsafe fn general_input(&mut self, event: &Ref<InputEvent>) {
        let save_event = event.assume_safe();

        // rotate camera horizontally
        let mouse_event = save_event.cast::<InputEventMouseMotion>(); // get the input as mouse input
        match mouse_event {
            Some(motion_event) => {
                let x_mov = motion_event.relative().x * self.mouse_sensitivity.x;
                self.rotate_cam_horizontal(x_mov);
            },
            _ => {}
        }

        let key_event = save_event.cast::<InputEventKey>();
        match key_event {
            Some(key_event) => {
                if key_event.scancode() == GlobalConstants::KEY_ESCAPE {
                    Input::godot_singleton().set_mouse_mode(Input::MOUSE_MODE_VISIBLE);       
                }
            },
            _ => {}
        }
    }

    /// this input method will be called when looking around, before taking a shot
    unsafe fn default_input(&mut self, event: Ref<InputEvent>) {
        let save_event = event.assume_safe();

        // left mouse button was pressed => switch to shooting mode
        if save_event.is_action_pressed(GodotString::from_str(&self.action_shooting), false, false) {
            godot_print!("mouse_button, switching to shooting mode");
            self.input_state = InputState::Shooting;
            return;
        }

        // rotate camera vertically
        let mouse_event = save_event.cast::<InputEventMouseMotion>(); // get the input as mouse input
        match mouse_event {
            Some(motion_event) => {
                let y_mov = -motion_event.relative().y * self.mouse_sensitivity.y;
                self.rotate_cam_vertical(y_mov);
            },
            _ => {}
        };
    }

    /// this input method will be called when player is currently taking a shot
    unsafe fn shooting_input(&mut self, event: Ref<InputEvent>) {
        let save_event = event.assume_safe();

        // mouse released, shoot
        if save_event.is_action_released(GodotString::from_str(&self.action_shooting), false) {
            self.input_state = InputState::Moving;
            self.shoot();
            self.current_force = 0.0;
            return;
        }

        // charge shot with vertical mouse movement
        let mouse_event = save_event.cast::<InputEventMouseMotion>(); // get the input as mouse input
        match mouse_event {
            Some(motion_event) => {
                let y_mov = motion_event.relative().y * self.shoot_sensitivity;
                self.current_force = match self.current_force + y_mov {
                    x if x < 0.0 => 0.0,
                    x if x > self.max_force => self.max_force,
                    x => x
                };

                godot_print!("current force: {}", self.current_force);
            },
            _ => {}
        };
    }

    /// this input method will be called when player is moving
    unsafe fn moving_input(&mut self, event: Ref<InputEvent>) {
        let save_event = event.assume_safe();

        // rotate camera vertically
        let mouse_event = save_event.cast::<InputEventMouseMotion>(); // get the input as mouse input
        match mouse_event {
            Some(motion_event) => {
                let y_mov = motion_event.relative().y * self.mouse_sensitivity.y;
                self.rotate_cam_vertical(y_mov);
            },
            _ => {}
        };
    }

    unsafe fn rotate_cam_horizontal(&mut self, input: f32) {
        // make sure the arm exists
        match self.node_camera_arm_horizontal {
            Some(arm) => {
                // rotate the horizontal camera arm
                let save_arm = arm.assume_safe();
                save_arm.rotate_object_local(Vector3 {x: 0.0, y: 1.0, z: 0.0}, input as f64)
            },
            _ => godot_warn!("No horizontal camera arm assigned.")
        }
    }

    unsafe fn rotate_cam_vertical(&mut self, input: f32) {
        // make sure the camera arm actually exists
        match self.node_camera_arm_vertical {
            Some(arm) => {
                // check for the current rotation
                let save_arm = arm.assume_safe();
                let current_rot = save_arm.rotation();

                // clamp the rotation
                if current_rot.x + input > self.camera_clamp.x || current_rot.x + input <= self.camera_clamp.y {
                    return;
                }

                // actually rotate if possible
                let save_arm = arm.assume_safe();
                save_arm.rotate_object_local(Vector3 {x: 1.0, y: 0.0, z: 0.0}, input as f64)
            },
            _ => godot_warn!("No vertical camera arm assigned")
        }
    }

    unsafe fn shoot(&mut self) {

        // make sure the camera actually exists
        let impulse_dir = match self.node_camera {
            None => { godot_warn!("No camera assigned!"); return; },
            Some(cam) => {
                // get the forward vector of the camera setting the up angle to the defined value in the editor
                let mut forward_vector = -cam.assume_safe().global_transform().basis.c().normalized();
                forward_vector.y = self.up_angle;

                // calculate the impulse force
                forward_vector.normalized() * self.current_force
            }
        };

        // get the die
        let die = match self.node_die {
            None => { godot_warn!("No W8 assigned."); return; },
            Some(node) => 
                match node.assume_safe().cast::<RigidBody>() {
                None => { godot_warn!("W8 is not a RigidBody."); return; },
                Some(rb) => rb
            }
        };

        // actually add the force
        die.apply_impulse(die.transform().origin, impulse_dir); 

        self.last_shot_time = OS::godot_singleton().get_ticks_msec();
    }
}