156 lines
6 KiB
GDScript
156 lines
6 KiB
GDScript
extends State
|
|
|
|
class_name ThrowState
|
|
|
|
var state_name : String = "Throw"
|
|
|
|
var play_char : CharacterBody3D
|
|
|
|
@export var aim_action : StringName = &"player_aim_camera"
|
|
|
|
@export_group("Timing")
|
|
@export var windup_time : float = 0.25 #delai avant que la balle parte (a caler sur l'animation)
|
|
@export var recovery_time : float = 0.35 #temps de recuperation apres le lancer
|
|
|
|
@export_group("Ball")
|
|
@export var ball_scene : PackedScene #modele de la balle (optionnel : sphere rouge par defaut)
|
|
@export var hand_height : float = 1.4
|
|
@export var ball_speed : float = 18.0
|
|
@export var min_flight_time : float = 0.25
|
|
@export var max_flight_time : float = 1.2
|
|
@export var arc_height : float = 1.5
|
|
@export var land_distance_from_target : float = 1.5 #la balle s'arrete devant une cible de combat
|
|
|
|
@export_group("Targeting")
|
|
@export var throw_range : float = 20.0
|
|
@export_flags_3d_physics var ground_mask : int = 1
|
|
|
|
var _elapsed : float = 0.0
|
|
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 _free_aim_point : Vector3 = Vector3.ZERO
|
|
|
|
func enter(play_char_ref : CharacterBody3D) -> void:
|
|
play_char = play_char_ref
|
|
_elapsed = 0.0
|
|
_thrown = false
|
|
_keep_aim = 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
|
|
|
|
play_char.floor_snap_length = 1.0
|
|
#play_char.godot_plush_skin.set_state("throw")
|
|
|
|
func exit() -> void:
|
|
#retour a la visee : on garde tout tel quel ; sinon on libere camera et verrouillage (comme AimState)
|
|
if _keep_aim:
|
|
_keep_aim = false
|
|
return
|
|
_release_aim()
|
|
|
|
func physics_update(delta : float) -> void:
|
|
if applies(): return
|
|
|
|
play_char.gravity_apply(delta)
|
|
slow_down(delta)
|
|
|
|
if _pokemon == null:
|
|
finish() #rien a lancer (equipe vide ou tous K.O.)
|
|
return
|
|
|
|
_elapsed += delta
|
|
if !_thrown and _elapsed >= windup_time:
|
|
throw_ball()
|
|
if _elapsed >= windup_time + recovery_time:
|
|
finish()
|
|
|
|
func applies() -> bool:
|
|
if !play_char.is_on_floor() and play_char.velocity.y < 0.0:
|
|
transitioned.emit(self, "InairState")
|
|
return true
|
|
return false
|
|
|
|
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)
|
|
|
|
func finish() -> void:
|
|
if Input.is_action_pressed(aim_action):
|
|
_keep_aim = true
|
|
transitioned.emit(self, "AimState")
|
|
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")
|
|
|
|
#---------- lancer ----------
|
|
|
|
func throw_ball() -> void:
|
|
_thrown = true
|
|
|
|
var from : Vector3 = play_char.global_position + Vector3.UP * hand_height
|
|
var to : Vector3 = _get_landing_point()
|
|
var flight_time : float = clampf(from.distance_to(to) / ball_speed, min_flight_time, max_flight_time)
|
|
|
|
var scene : Node = play_char.get_tree().current_scene
|
|
var ball : ThrownBall = ThrownBall.launch(scene, from, to, flight_time, arc_height, ball_scene)
|
|
#la balle est autonome : l'atterrissage est gere ici meme si le joueur a change d'etat entre-temps
|
|
ball.landed.connect(_on_ball_landed.bind(_pokemon, _target, from))
|
|
|
|
func _get_landing_point() -> Vector3:
|
|
if !is_instance_valid(_target): return _free_aim_point
|
|
|
|
var target_pos : Vector3 = _target.global_position
|
|
if _starts_battle(_target):
|
|
#la balle s'arrete devant la cible, du cote du joueur
|
|
var to_player : Vector3 = play_char.global_position - target_pos
|
|
to_player.y = 0.0
|
|
if to_player.length() > land_distance_from_target:
|
|
target_pos += to_player.normalized() * land_distance_from_target
|
|
return _snap_to_ground(target_pos)
|
|
|
|
func _compute_free_aim_point() -> Vector3:
|
|
#point vise au centre de l'ecran : premier obstacle, sinon fin de portee ramenee au sol
|
|
var cam : Camera3D = play_char.cam_holder.cam
|
|
var origin : Vector3 = cam.global_position
|
|
var far : Vector3 = origin - cam.global_basis.z * throw_range
|
|
var query := PhysicsRayQueryParameters3D.create(origin, far, ground_mask, [play_char.get_rid()])
|
|
var hit : Dictionary = play_char.get_world_3d().direct_space_state.intersect_ray(query)
|
|
if hit.is_empty(): return _snap_to_ground(far)
|
|
return _snap_to_ground(hit["position"] + hit["normal"] * 0.5)
|
|
|
|
func _snap_to_ground(point : Vector3) -> Vector3:
|
|
var query := PhysicsRayQueryParameters3D.create(point + Vector3.UP * 2.0, point + Vector3.DOWN * 6.0, ground_mask, [play_char.get_rid()])
|
|
var hit : Dictionary = play_char.get_world_3d().direct_space_state.intersect_ray(query)
|
|
return hit["position"] if !hit.is_empty() else point
|
|
|
|
func _starts_battle(target : Object) -> bool:
|
|
#AimTarget doit avoir : @export var starts_battle : bool = false
|
|
return "starts_battle" in target and target.starts_battle
|
|
|
|
func _on_ball_landed(landing_pos : Vector3, pokemon : PokemonInstance, target : Variant, thrower_pos : Vector3) -> void:
|
|
var scene : Node = get_tree().current_scene if is_inside_tree() else null
|
|
if scene == null: return
|
|
|
|
var target_valid : bool = is_instance_valid(target)
|
|
#le pokemon regarde la cible, ou dans la direction du lancer s'il n'y en a pas
|
|
var look_pos : Vector3 = target.global_position if target_valid else landing_pos + (landing_pos - thrower_pos)
|
|
var sent : SentPokemon = SentPokemon.send(scene, pokemon, landing_pos, look_pos)
|
|
|
|
if target_valid and _starts_battle(target):
|
|
GameData.battle_requested.emit(sent, target)
|
|
|
|
#---------- nettoyage ----------
|
|
|
|
func _release_aim() -> void:
|
|
#meme comportement que AimState.exit() / release_target()
|
|
if !play_char: return
|
|
if is_instance_valid(play_char.aim_target): play_char.aim_target.set_locked(false)
|
|
play_char.aim_target = null
|
|
play_char.cam_holder.set_lock_target(null)
|
|
play_char.cam_holder.cam_aimed = false
|