class_name MountRider extends PlayerComponent #composant a ajouter en enfant de PlayerCharacter (a cote de InputManagementComponent / RecallInput). #gere la monte d'un SentPokemon dont l'espece a un MountProfile (voir MountRegistry) : # - s'approcher + appuyer sur mount_action (A) pour monter, dismount_action pour descendre # - le joueur garde ses etats Idle / Walk / Run / Jump / Aim / Throw # - Dodge est refuse par la StateMachine et le crouch est desactive (player.can_crouch) tant qu'on est monte # - vitesse, accel et saut sont remplaces par ceux du MountProfile # #le travail est reparti en trois classes : # - MountRider (ici) : cycle monter / descendre, etats bloques, hints, signaux # - MountDetector : trouve le pokemon montable le plus proche # - MountSeat : la monture suit le joueur, le joueur est assis a la selle, la camera monte #l'appui qui monte ou descend est consomme (InputManagementComponent.consume) : il ne declenche pas en plus un saut, une roulade ou un crouch. signal mounted(sent : SentPokemon) signal dismounted(sent : SentPokemon) signal candidate_changed(sent : SentPokemon) #pokemon montable a portee (null = aucun) : pour afficher "A : Monter" dans le HUD #@export var mount_action : StringName = &"player_interact" #A (manette) / E (clavier) : cree automatiquement s'il n'existe pas #@export var dismount_action : StringName = &"player_dismount" #Y (manette) / F (clavier) : cree automatiquement s'il n'existe pas @export var mount_range : float = 2.5 #distance horizontale max entre le joueur et le pokemon @export var max_height_diff : float = 2.0 @export var blocked_states : PackedStringArray = [States.DODGE] #etats refuses pendant la monte @export var swallow_jump_on_mount : bool = true #si A sert aussi a sauter : l'appui qui monte ne declenche pas de saut @export var camera_follow_speed : float = 8.0 #vitesse a laquelle la camera monte a hauteur de selle @export_group("Hints") @export var mount_hint : Hint #mount.tres @export var dismount_hint : Hint #dismount.tres @export var jump_hint : Hint #jump.tres var mount_action : StringName: get: return mount_hint.action var dismount_action : StringName: get: return dismount_hint.action var mount : SentPokemon = null var profile : MountProfile = null var candidate : SentPokemon = null var _seat : MountSeat var _could_dismount : bool = false #derniere valeur de _can_switch_now() pendant la monte : les hints ne sont rafraichis que si elle change func _ready() -> void: process_physics_priority = -10 #avant la StateMachine : l'appui qui monte est ainsi consomme avant que les etats ne le lisent _seat = MountSeat.new(player, player.visual_root, player.cam_holder) InputRegistry.ensure(mount_action, [InputRegistry.pad_button(JOY_BUTTON_A), InputRegistry.key(KEY_E)]) InputRegistry.ensure(dismount_action, [InputRegistry.pad_button(JOY_BUTTON_X), InputRegistry.key(KEY_F)]) func _exit_tree() -> void: Hints.clear_hints(self) func is_mounted() -> bool: return mount != null func _physics_process(_delta : float) -> void: if mount != null: var can_dismount : bool = _can_switch_now() if can_dismount != _could_dismount: #le hint de descente apparait / disparait (pas de descente en plein vol) _could_dismount = can_dismount _refresh_hints() if Input.is_action_just_pressed(dismount_action) and can_dismount: dismount() return _set_candidate(MountDetector.find_candidate(player, mount_range, max_height_diff) if _can_switch_now() else null) if candidate != null and Input.is_action_just_pressed(mount_action): mount_up(candidate) func _process(delta : float) -> void: if mount != null and !is_instance_valid(mount): dismount() #le pokemon a disparu sans passer par recall() _seat.update(delta, camera_follow_speed) func _can_switch_now() -> bool: return player.is_on_floor() and !player.is_crouching and !player.is_aiming and player.state_machine.curr_state is GroundedState #ni accroupi ni en visant (monter / descendre : depuis le sol seulement) #---------- hints ---------- func _refresh_hints() -> void: #une seule source de verite pour les hints de la monture var list : Array[Hint] = [] if mount != null: if _can_switch_now(): list.append(dismount_hint) #pas de descente en plein vol list.append(jump_hint) elif candidate != null: list.append(mount_hint) Hints.show_hints(self, list) func _set_candidate(s : SentPokemon) -> void: if s == candidate: return candidate = s candidate_changed.emit(s) _refresh_hints() #---------- monter / descendre ---------- func mount_up(s : SentPokemon) -> void: if mount != null: return mount = s profile = MountRegistry.get_profile(s.pokemon.species) s.rider = player s.recalled.connect(_on_mount_recalled) _could_dismount = _can_switch_now() _set_candidate(null) _refresh_hints() _seat.attach(s, profile) player.is_mounted = true player.enter_mount_stats(profile.mount_speed, profile.mount_accel, profile.mount_deccel, -1.0 if profile.can_use_surface(MountProfile.Surface.AIR) else profile.mount_jump_height) #monture volante : le vol libre remplacera le saut for state_name in blocked_states: player.state_machine.set_state_blocked(state_name, true) player.can_crouch = false #pas de crouch a dos de pokemon (voir GroundedState) #l'appui qui monte ne doit pas faire sauter (si A sert aussi a sauter) : on le consomme jusqu'a ce que le bouton soit relache if swallow_jump_on_mount: player.player_input.consume(player.player_input.jump_action) mounted.emit(s) func dismount() -> void: if mount == null: return var s : SentPokemon = mount if is_instance_valid(mount) else null #le pokemon a pu disparaitre sans passer par recall() mount = null profile = null _refresh_hints() _seat.detach() player.is_mounted = false player.exit_mount_stats() #plus rien n'est bloque : l'appui qui descend ne declenche ni roulade ni crouch (consomme jusqu'au relachement du bouton) for state_name in blocked_states: player.state_machine.set_state_blocked(state_name, false) player.can_crouch = true player.player_input.consume(player.player_input.dodge_action) player.player_input.consume(player.player_input.crouch_action) if s != null: s.rider = null if s.recalled.is_connected(_on_mount_recalled): s.recalled.disconnect(_on_mount_recalled) dismounted.emit(s) func _on_mount_recalled() -> void: dismount() #le pokemon monte vient d'etre rappele (ex : relance de ce meme pokemon depuis le HUD)