diff --git a/game/player/states/aim_state.gd b/game/player/states/aim_state.gd index 129127f..ef32d77 100644 --- a/game/player/states/aim_state.gd +++ b/game/player/states/aim_state.gd @@ -43,7 +43,10 @@ func enter(play_char_ref : CharacterBody3D) -> void: play_char = play_char_ref auto_lock_blocked = false #curr_state_name n'est pas encore mis a jour a ce stade : il reflete encore l'etat qu'on est en train de quitter - came_from_crouch = play_char.state_machine.curr_state_name == "Crouch" + #si on revient de ThrowState, came_from_crouch a deja ete transmis par ThrowState : on ne l'ecrase pas + var previous : String = play_char.state_machine.curr_state_name + if previous != "Throw" and previous != "Dodge": + came_from_crouch = previous == "Crouch" verifications() func verifications() -> void: diff --git a/game/player/states/state_machine.gd b/game/player/states/state_machine.gd index 8eb1d80..8e49d86 100644 --- a/game/player/states/state_machine.gd +++ b/game/player/states/state_machine.gd @@ -3,6 +3,9 @@ extends Node class_name StateMachine @export var initial_state : State +@export var throw_action : StringName = &"player_throw" + +const THROW_ENTRY_STATES : Array[String] = ["Idle", "Walk", "Run", "Crouch"] #etats depuis lesquels RT lance sans viser var curr_state : State var curr_state_name : String @@ -26,9 +29,30 @@ func _ready() -> void: func _process(delta : float) -> void: if curr_state: curr_state.update(delta) - + + func _physics_process(delta: float) -> void: - if curr_state: curr_state.physics_update(delta) + if curr_state: + if try_start_throw(): return + curr_state.physics_update(delta) + +func try_start_throw() -> bool: + if !(curr_state_name in THROW_ENTRY_STATES): return false + if !Input.is_action_just_pressed(throw_action): return false + if !char_ref.is_on_floor(): return false + + var selected : PokemonInstance = GameData.get_selected_pokemon() + if selected == null: return false + + #pokemon deja dehors : l'appui le rappelle au lieu de lancer + var already_out : SentPokemon = SentPokemon.find_for(get_tree(), selected) + if already_out: + already_out.recall() + return false + + if !states.has("throwstate"): return false + on_state_child_transition(curr_state, "ThrowState") + return true func on_state_child_transition(state : State, new_state_name : String) -> void: #manage the transition from one state to another diff --git a/game/player/states/throw_state.gd b/game/player/states/throw_state.gd index 7d7e191..61145d1 100644 --- a/game/player/states/throw_state.gd +++ b/game/player/states/throw_state.gd @@ -7,9 +7,15 @@ var state_name : String = "Throw" var play_char : CharacterBody3D @export var aim_action : StringName = &"player_aim_camera" +@export var throw_action : StringName = &"player_throw" #RT / clic gauche : maintenir pour charger, relacher pour lancer +@export var cancel_action : StringName = &"player_throw_cancel" #B / clic droit (cree automatiquement s'il n'existe pas) + +@export_group("Charge") +@export var charge_move_speed : float = 3.0 #vitesse de deplacement pendant que RT est maintenue +@export var charge_move_accel : float = 8.0 @export_group("Timing") -@export var windup_time : float = 0.25 #delai avant que la balle parte (a caler sur l'animation) +@export var windup_time : float = 0.25 #delai entre le relachement et le depart de la balle (a caler sur l'animation) @export var recovery_time : float = 0.35 #temps de recuperation apres le lancer @export_group("Ball") @@ -26,21 +32,56 @@ var play_char : CharacterBody3D @export_flags_3d_physics var ground_mask : int = 1 var _elapsed : float = 0.0 +var _released : bool = false #true des que RT est relachee (phase windup/recovery) var _thrown : bool = false var _keep_aim : bool = false #true quand on retourne a AimState : on garde camera et verrouillage var _pokemon : PokemonInstance = null -var _target : Variant = null #AimTarget verrouille au moment du lancer +var _target : Variant = null #AimTarget verrouille au moment du relachement var _free_aim_point : Vector3 = Vector3.ZERO +var _came_from_crouch : bool = false #true si le lancer vient du crouch (directement, ou via Aim declenche depuis le crouch) + +func _ready() -> void: + _ensure_action(throw_action, func(): + var trigger := InputEventJoypadMotion.new() + trigger.axis = JOY_AXIS_TRIGGER_RIGHT + trigger.axis_value = 1.0 + InputMap.action_add_event(throw_action, trigger) + var click := InputEventMouseButton.new() + click.button_index = MOUSE_BUTTON_LEFT + InputMap.action_add_event(throw_action, click)) + _ensure_action(cancel_action, func(): + var pad := InputEventJoypadButton.new() + pad.button_index = JOY_BUTTON_B + InputMap.action_add_event(cancel_action, pad) + var click := InputEventMouseButton.new() + click.button_index = MOUSE_BUTTON_RIGHT + InputMap.action_add_event(cancel_action, click)) + +func _ensure_action(action : StringName, add_events : Callable) -> void: + if InputMap.has_action(action): return + InputMap.add_action(action) + add_events.call() func enter(play_char_ref : CharacterBody3D) -> void: play_char = play_char_ref _elapsed = 0.0 + _released = false _thrown = false _keep_aim = false + #curr_state_name n'est pas encore mis a jour a ce stade : il reflete encore l'etat qu'on quitte + var previous : String = play_char.state_machine.curr_state_name + if previous == "Crouch": + _came_from_crouch = true + elif previous == "Aim": + var aim : Node = play_char.state_machine.states.get("aimstate") + _came_from_crouch = aim != null and aim.came_from_crouch + else: + _came_from_crouch = false + _pokemon = GameData.get_selected_pokemon() - _target = play_char.aim_target if is_instance_valid(play_char.aim_target) else null - _free_aim_point = _compute_free_aim_point() #visee libre : le point est fige au moment ou on appuie + _target = null + _free_aim_point = Vector3.ZERO play_char.floor_snap_length = 1.0 #play_char.godot_plush_skin.set_state("throw") @@ -56,12 +97,24 @@ func physics_update(delta : float) -> void: if applies(): return play_char.gravity_apply(delta) - slow_down(delta) if _pokemon == null: + slow_down(delta) finish() #rien a lancer (equipe vide ou tous K.O.) return + #phase 1 : charge, tant que RT est maintenue + if !_released: + charge(delta) + return + + #phase 2 : windup puis recovery + slow_down(delta) + + if !_thrown and Input.is_action_just_pressed(cancel_action): + finish() #annule avant que la balle parte + return + _elapsed += delta if !_thrown and _elapsed >= windup_time: throw_ball() @@ -74,6 +127,35 @@ func applies() -> bool: return true return false +#---------- charge ---------- + +func charge(delta : float) -> void: + if Input.is_action_just_pressed(cancel_action): + finish() #annule : rien n'est lance + return + + if !Input.is_action_pressed(throw_action): + release() + return + + move_while_charging(delta) + +func release() -> void: + #RT relachee : on fige la cible et le point vise, puis le windup demarre + _released = true + _elapsed = 0.0 + _target = play_char.aim_target if is_instance_valid(play_char.aim_target) else null + _free_aim_point = _compute_free_aim_point() + +func move_while_charging(delta : float) -> void: + play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y) + + if play_char.move_dir and play_char.is_on_floor(): + play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * charge_move_speed, charge_move_accel * delta) + play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * charge_move_speed, charge_move_accel * delta) + else: + slow_down(delta) + func slow_down(delta : float) -> void: play_char.velocity.x = lerp(play_char.velocity.x, 0.0, play_char.move_deccel * delta) play_char.velocity.z = lerp(play_char.velocity.z, 0.0, play_char.move_deccel * delta) @@ -81,9 +163,16 @@ func slow_down(delta : float) -> void: func finish() -> void: if Input.is_action_pressed(aim_action): _keep_aim = true + #AimState ne verra que "Throw" comme etat precedent : on lui transmet l'origine crouch + var aim : Node = play_char.state_machine.states.get("aimstate") + if aim: aim.came_from_crouch = _came_from_crouch transitioned.emit(self, "AimState") return + if _came_from_crouch: + transitioned.emit(self, "CrouchState") + return + var dir : Vector2 = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action) transitioned.emit(self, play_char.walk_or_run if dir != Vector2.ZERO else "IdleState")