diff --git a/data/hints/cancel_throw.tres b/data/hints/cancel_throw.tres new file mode 100644 index 0000000..8767971 --- /dev/null +++ b/data/hints/cancel_throw.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="Hint" format=3 uid="uid://bow3d0e0fcto7"] + +[ext_resource type="Script" uid="uid://b1rvj44fyaea4" path="res://game/ui/hints/hint.gd" id="1_3q5e6"] + +[resource] +script = ExtResource("1_3q5e6") +action = &"player_throw_cancel" +label = "Annuler le lancer" +metadata/_custom_type_script = "uid://b1rvj44fyaea4" diff --git a/data/hints/dismount.tres b/data/hints/dismount.tres new file mode 100644 index 0000000..50f9316 --- /dev/null +++ b/data/hints/dismount.tres @@ -0,0 +1,10 @@ +[gd_resource type="Resource" script_class="Hint" format=3 uid="uid://cfl2q6f3sjy1v"] + +[ext_resource type="Script" uid="uid://b1rvj44fyaea4" path="res://game/ui/hints/hint.gd" id="1_dm3jb"] + +[resource] +script = ExtResource("1_dm3jb") +action = &"player_dismount" +label = "Descendre" +priority = 10 +metadata/_custom_type_script = "uid://b1rvj44fyaea4" diff --git a/data/hints/jump.tres b/data/hints/jump.tres new file mode 100644 index 0000000..83017ad --- /dev/null +++ b/data/hints/jump.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="Hint" format=3 uid="uid://cdx4qndy5xwto"] + +[ext_resource type="Script" uid="uid://b1rvj44fyaea4" path="res://game/ui/hints/hint.gd" id="1_ch8ap"] + +[resource] +script = ExtResource("1_ch8ap") +action = &"player_jump" +label = "Sauter" +metadata/_custom_type_script = "uid://b1rvj44fyaea4" diff --git a/data/hints/mount.tres b/data/hints/mount.tres new file mode 100644 index 0000000..ab23c97 --- /dev/null +++ b/data/hints/mount.tres @@ -0,0 +1,10 @@ +[gd_resource type="Resource" script_class="Hint" format=3 uid="uid://c18ntr4wb56m2"] + +[ext_resource type="Script" uid="uid://b1rvj44fyaea4" path="res://game/ui/hints/hint.gd" id="1_aaq06"] + +[resource] +script = ExtResource("1_aaq06") +action = &"player_interact" +label = "Grimper" +priority = 10 +metadata/_custom_type_script = "uid://b1rvj44fyaea4" diff --git a/data/hints/switch_target.tres b/data/hints/switch_target.tres new file mode 100644 index 0000000..a37923d --- /dev/null +++ b/data/hints/switch_target.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="Hint" format=3 uid="uid://c4fqcdrf6il7v"] + +[ext_resource type="Script" uid="uid://b1rvj44fyaea4" path="res://game/ui/hints/hint.gd" id="1_gggbv"] + +[resource] +script = ExtResource("1_gggbv") +action = &"player_aim_switch_target" +label = "Changer de cible" +metadata/_custom_type_script = "uid://b1rvj44fyaea4" diff --git a/data/hints/talk.tres b/data/hints/talk.tres new file mode 100644 index 0000000..cdec8ad --- /dev/null +++ b/data/hints/talk.tres @@ -0,0 +1,10 @@ +[gd_resource type="Resource" script_class="Hint" format=3 uid="uid://4dptmdqsnh0c"] + +[ext_resource type="Script" uid="uid://b1rvj44fyaea4" path="res://game/ui/hints/hint.gd" id="1_j40kh"] + +[resource] +script = ExtResource("1_j40kh") +action = &"player_interact" +label = "Parler" +priority = 10 +metadata/_custom_type_script = "uid://b1rvj44fyaea4" diff --git a/game/interaction/interactable.gd b/game/interaction/interactable.gd new file mode 100644 index 0000000..888a360 --- /dev/null +++ b/game/interaction/interactable.gd @@ -0,0 +1,41 @@ +class_name Interactable +extends Area3D + +signal interacted(player : PlayerCharacter) + +@export var hint : Hint #talk.tres, etc. +@export var enabled : bool = true: + set(value): + enabled = value + _refresh() + +var _player : PlayerCharacter = null + +func _ready() -> void: + assert(hint != null, "Interactable : aucun Hint assigne sur %s" % get_path()) + body_entered.connect(_on_body_entered) + body_exited.connect(_on_body_exited) + _refresh() + +func _on_body_entered(body : Node3D) -> void: + if !(body is PlayerCharacter): return + _player = body + _refresh() + +func _on_body_exited(body : Node3D) -> void: + if body != _player: return + _player = null + _refresh() + +func _refresh() -> void: + var active : bool = enabled and _player != null + set_physics_process(active) + if active: Hints.show_hints(self, [hint]) + else: Hints.clear_hints(self) + +func _physics_process(_delta : float) -> void: + if Input.is_action_just_pressed(hint.action): + interacted.emit(_player) + +func _exit_tree() -> void: + Hints.clear_hints(self) diff --git a/game/interaction/interactable.gd.uid b/game/interaction/interactable.gd.uid new file mode 100644 index 0000000..e6bbbb0 --- /dev/null +++ b/game/interaction/interactable.gd.uid @@ -0,0 +1 @@ +uid://b7favvk6hxvru diff --git a/game/mount/mount_rider.gd b/game/mount/mount_rider.gd index b448075..2abf744 100644 --- a/game/mount/mount_rider.gd +++ b/game/mount/mount_rider.gd @@ -12,17 +12,23 @@ 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_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 = ["DodgeState", "CrouchState"] #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 HUD") -@export var mount_hint_text : String = "Grimper" -@export var hint_priority : int = 10 +@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 const GROUNDED_STATES : Array[String] = ["Idle", "Walk", "Run"] #monter / descendre n'est possible que depuis ces etats @@ -55,8 +61,18 @@ func _ready() -> void: key.physical_keycode = KEY_F InputMap.action_add_event(dismount_action, key)) +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 _exit_tree() -> void: - Hints.clear_context(self) + Hints.clear_hints(self) func _ensure_action(action : StringName, add_events : Callable) -> void: if InputMap.has_action(action): return @@ -69,8 +85,7 @@ func is_mounted() -> bool: func _physics_process(_delta : float) -> void: if mount != null: var can_dismount : bool = _can_switch_now() - if can_dismount: Hints.set_context(self, dismount_action, "", hint_priority) - else: Hints.clear_context(self) + _refresh_hints() if Input.is_action_just_pressed(dismount_action) and can_dismount: dismount() return @@ -133,8 +148,7 @@ func _set_candidate(s : SentPokemon) -> void: if s == candidate: return candidate = s candidate_changed.emit(s) - if s != null: Hints.set_context(self, mount_action, mount_hint_text, hint_priority) - else: Hints.clear_context(self) + _refresh_hints() #---------- monter / descendre ---------- @@ -145,7 +159,7 @@ func mount_up(s : SentPokemon) -> void: s.rider = player s.recalled.connect(_on_mount_recalled) _set_candidate(null) - Hints.set_context(self, dismount_action, "", hint_priority) + _refresh_hints() player.is_mounted = true player.enter_mount_stats(profile.mount_speed, profile.mount_accel, profile.mount_deccel, @@ -161,10 +175,10 @@ func dismount() -> void: var s : SentPokemon = mount mount = null profile = null + _refresh_hints() player.is_mounted = false player.exit_mount_stats() - Hints.clear_context(self) _release_blocked_states_next_frame() visual_root.position -= _seat_applied diff --git a/game/player/player_character.tscn b/game/player/player_character.tscn index 87f8a5f..c40a147 100644 --- a/game/player/player_character.tscn +++ b/game/player/player_character.tscn @@ -7,14 +7,20 @@ [ext_resource type="Script" uid="uid://dy4wrpvpep1jq" path="res://game/player/states/state_machine.gd" id="3_o4naa"] [ext_resource type="Script" uid="uid://bf6hjwfa74ttj" path="res://game/player/states/idle_state.gd" id="4_38lxl"] [ext_resource type="Script" uid="uid://2agliol5dulc" path="res://game/player/states/walk_state.gd" id="4_lg056"] +[ext_resource type="Script" uid="uid://b1rvj44fyaea4" path="res://game/ui/hints/hint.gd" id="6_7qhxe"] [ext_resource type="Script" uid="uid://dr5p8ygj25w6x" path="res://game/player/states/jump_state.gd" id="6_lwgph"] +[ext_resource type="Resource" uid="uid://cdx4qndy5xwto" path="res://data/hints/jump.tres" id="7_56w3b"] [ext_resource type="Script" uid="uid://dn5idrvu68k83" path="res://game/player/states/aim_state.gd" id="7_kxr4l"] [ext_resource type="Script" uid="uid://dpcfffs3vt5co" path="res://game/player/states/run_state.gd" id="8_j4mf1"] [ext_resource type="Script" uid="uid://c3jh22p4w3xco" path="res://game/player/states/throw_state.gd" id="8_k0r6x"] [ext_resource type="Script" uid="uid://bwojkp7yrwswt" path="res://game/player/states/dodge_state.gd" id="9_6abew"] [ext_resource type="Script" uid="uid://d0577tj0c23bu" path="res://game/player/states/crouch_state.gd" id="10_mi63v"] +[ext_resource type="Resource" uid="uid://c4fqcdrf6il7v" path="res://data/hints/switch_target.tres" id="11_lapey"] +[ext_resource type="Resource" uid="uid://bow3d0e0fcto7" path="res://data/hints/cancel_throw.tres" id="12_iqmup"] [ext_resource type="Script" uid="uid://c820hnwuwpmlg" path="res://game/player/input/recall_input.gd" id="13_siyau"] [ext_resource type="Script" uid="uid://c4vkkdl0an244" path="res://game/mount/mount_rider.gd" id="14_dkijf"] +[ext_resource type="Resource" uid="uid://c18ntr4wb56m2" path="res://data/hints/mount.tres" id="20_lapey"] +[ext_resource type="Resource" uid="uid://cfl2q6f3sjy1v" path="res://data/hints/dismount.tres" id="21_iqmup"] [sub_resource type="Curve" id="Curve_kxr4l"] _limits = [0.0, 10.0, 0.0, 1.0] @@ -81,28 +87,32 @@ initial_state = NodePath("IdleState") [node name="IdleState" type="Node" parent="StateMachine" unique_id=1417869633] script = ExtResource("4_38lxl") -hints = Array[StringName]([&"player_jump"]) +hints = Array[ExtResource("6_7qhxe")]([ExtResource("7_56w3b")]) [node name="WalkState" type="Node" parent="StateMachine" unique_id=1524475809] script = ExtResource("4_lg056") -hints = Array[StringName]([&"player_jump"]) +hints = Array[ExtResource("6_7qhxe")]([ExtResource("7_56w3b")]) [node name="JumpState" type="Node" parent="StateMachine" unique_id=711160985] script = ExtResource("6_lwgph") -hints = Array[StringName]([&"player_jump"]) +hints = Array[ExtResource("6_7qhxe")]([ExtResource("7_56w3b")]) [node name="AimState" type="Node" parent="StateMachine" unique_id=296560188] script = ExtResource("7_kxr4l") +switch_hint = ExtResource("11_lapey") +cancel_hint = ExtResource("12_iqmup") [node name="ThrowState" type="Node" parent="StateMachine" unique_id=1159570489] script = ExtResource("8_k0r6x") +cancel_hint = ExtResource("12_iqmup") [node name="RunState" type="Node" parent="StateMachine" unique_id=1345421775] script = ExtResource("8_j4mf1") -hints = Array[StringName]([&"player_jump"]) +hints = Array[ExtResource("6_7qhxe")]([ExtResource("7_56w3b")]) [node name="DodgeState" type="Node" parent="StateMachine" unique_id=232179400] script = ExtResource("9_6abew") +keep_previous_hints = true [node name="CrouchState" type="Node" parent="StateMachine" unique_id=381419367] script = ExtResource("10_mi63v") @@ -115,3 +125,6 @@ script = ExtResource("13_siyau") [node name="MountRider" type="Node" parent="." unique_id=639128028] script = ExtResource("14_dkijf") +mount_hint = ExtResource("20_lapey") +dismount_hint = ExtResource("21_iqmup") +jump_hint = ExtResource("7_56w3b") diff --git a/game/player/states/aim_state.gd b/game/player/states/aim_state.gd index d3df3c8..346ba12 100644 --- a/game/player/states/aim_state.gd +++ b/game/player/states/aim_state.gd @@ -7,10 +7,10 @@ var state_name : String = "Aim" var play_char : CharacterBody3D @export var aim_action : StringName = &"player_aim_camera" -@export var switch_target_action : StringName = &"player_aim_switch_target" +#@export var switch_target_action : StringName = &"player_aim_switch_target" @export var unlock_action : StringName = &"player_aim_unlock" @export var throw_action : StringName = &"player_throw" #RT / clic gauche (cree automatiquement s'il n'existe pas) -@export var cancel_action : StringName = &"player_throw_cancel" #annule la charge d'un lancer (cree par ThrowState) +#@export var cancel_action : StringName = &"player_throw_cancel" #annule la charge d'un lancer (cree par ThrowState) @export_group("Targeting") @export var throw_range : float = 20.0 @@ -24,6 +24,15 @@ var play_char : CharacterBody3D @export var aim_move_speed : float = 3.0 @export var aim_move_accel : float = 8.0 +@export_group("Hints") +@export var switch_hint : Hint #switch_target.tres +@export var cancel_hint : Hint #cancel_throw.tres (la meme ressource que ThrowState) + +var switch_target_action : StringName: + get: return switch_hint.action +var cancel_action : StringName: + get: return cancel_hint.action + var auto_lock_blocked : bool = false #true apres un deverrouillage manuel, tant que la cible est encore au centre var dodging_from_aim : bool = false #true quand on quitte Aim pour rouler, pour ne pas relacher la cible verrouillee var came_from_crouch : bool = false #true si Aim a ete declenche depuis CrouchState, pour y retourner en sortant @@ -239,7 +248,7 @@ func release_target() -> void: play_char.aim_target = null play_char.cam_holder.set_lock_target(null) -func get_hints() -> Array: - var list : Array[StringName] = [switch_target_action] - if charging_throw: list.append(cancel_action) +func get_hints() -> Array[Hint]: + var list : Array[Hint] = [switch_hint] + if charging_throw: list.append(cancel_hint) return list diff --git a/game/player/states/mounted_state.gd b/game/player/states/mounted_state.gd index 1760aca..3b6a1f4 100644 --- a/game/player/states/mounted_state.gd +++ b/game/player/states/mounted_state.gd @@ -14,6 +14,3 @@ func enter(play_char_ref : CharacterBody3D) -> void: play_char.move_deccel = profile.mount_deccel # instancier profile.mount_scene_override ou profile.species.model_scene, # le positionner, y attacher play_char selon seat_offset... - -func get_hints() -> Array: - return [{"action": &"player_dismount", "text": "Descendre"}] # remplace par le nom réel de ton action diff --git a/game/player/states/state.gd b/game/player/states/state.gd index b3c7333..a08fa22 100644 --- a/game/player/states/state.gd +++ b/game/player/states/state.gd @@ -3,25 +3,22 @@ extends Node class_name State signal transitioned #it's normal if this signal isn't used in this script, because it's purpose is to be used in the scripts that extends from the State class -signal hints_changed #à émettre quand get_hints() change pendant que l'état est actif +signal hints_changed #a emettre quand get_hints() change alors que l'etat est actif -@export var hints : Array[StringName] = [] #hints simples, réglables dans l'inspecteur +@export var hints : Array[Hint] = [] #cas simple : se regle dans l'inspecteur +@export var keep_previous_hints : bool = false #etats transitoires (Dodge...) : on ne touche pas aux hints affiches func enter(_char_reference : CharacterBody3D) -> void: - #enter state pass func exit() -> void: - #exit state pass func update(_delta : float) -> void: - #process update pass func physics_update(_delta : float) -> void: - #physics_process update pass -func get_hints() -> Array: - return hints #à surcharger seulement si les hints dépendent d'une condition +func get_hints() -> Array[Hint]: + return hints #a surcharger seulement quand les hints dependent d'une condition diff --git a/game/player/states/state_machine.gd b/game/player/states/state_machine.gd index fba5287..757a13c 100644 --- a/game/player/states/state_machine.gd +++ b/game/player/states/state_machine.gd @@ -20,7 +20,7 @@ func _ready() -> void: if child is State: states[child.name.to_lower()] = child child.transitioned.connect(on_state_child_transition) - child.hints_changed.connect(_on_hints_changed.bind(child)) + child.hints_changed.connect(_on_state_hints_changed.bind(child)) #if initial state, transition to it if initial_state: @@ -81,11 +81,12 @@ func set_state_blocked(state_name : String, blocked : bool) -> void: if blocked: blocked_states[state_name.to_lower()] = true else: blocked_states.erase(state_name.to_lower()) -func _on_hints_changed(state : State) -> void: +func _on_state_hints_changed(state : State) -> void: if state == curr_state: publish_hints() func publish_hints() -> void: - if !curr_state: return - var actions = curr_state.get_hints() - if actions != null: Hints.set_state_hints(actions) #null = on garde les hints précédents (si tu as gardé cette option pour Dodge) - + if curr_state and !curr_state.keep_previous_hints: + Hints.show_hints(self, curr_state.get_hints()) + +func _exit_tree() -> void: + Hints.clear_hints(self) diff --git a/game/player/states/throw_state.gd b/game/player/states/throw_state.gd index 1f660c4..c9997f4 100644 --- a/game/player/states/throw_state.gd +++ b/game/player/states/throw_state.gd @@ -8,7 +8,11 @@ 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 var cancel_action : StringName = &"player_throw_cancel" #B / clic droit (cree automatiquement s'il n'existe pas) + +@export var cancel_hint : Hint #cancel_throw.tres +var cancel_action : StringName: #l'action vient du Hint : une seule definition + get: return cancel_hint.action @export_group("Charge") @export var charge_move_speed : float = 3.0 #vitesse de deplacement pendant que RT est maintenue @@ -211,6 +215,7 @@ func update_reticle() -> void: func throw_ball() -> void: _thrown = true + hints_changed.emit() AimReticle.hide_instance() var from : Vector3 = play_char.global_position + Vector3.UP * hand_height @@ -295,7 +300,7 @@ func _release_aim() -> void: play_char.cam_holder.set_lock_target(null) play_char.cam_holder.cam_aimed = false -func get_hints() -> Array: - var list : Array[StringName] = [] - if !_thrown: list.append(cancel_action) #plus d'annulation possible une fois la balle partie +func get_hints() -> Array[Hint]: + var list : Array[Hint] = [] + if !_thrown: list.append(cancel_hint) #plus d'annulation possible une fois la balle partie return list diff --git a/game/ui/hints/hint.gd b/game/ui/hints/hint.gd new file mode 100644 index 0000000..49bd5eb --- /dev/null +++ b/game/ui/hints/hint.gd @@ -0,0 +1,6 @@ +class_name Hint +extends Resource + +@export var action : StringName #action de l'Input Map : sert à la fois à l'icône et au code d'input +@export var label : String #libellé affiché (passe par tr() dans le HUD) +@export var priority : int = 0 #le plus haut passe en premier ; à action égale, le plus prioritaire gagne diff --git a/game/ui/hints/hint.gd.uid b/game/ui/hints/hint.gd.uid new file mode 100644 index 0000000..a799dc0 --- /dev/null +++ b/game/ui/hints/hint.gd.uid @@ -0,0 +1 @@ +uid://b1rvj44fyaea4 diff --git a/game/ui/hints/hint_row.gd b/game/ui/hints/hint_row.gd new file mode 100644 index 0000000..4015c73 --- /dev/null +++ b/game/ui/hints/hint_row.gd @@ -0,0 +1,11 @@ +class_name HintRow +extends HBoxContainer + +@onready var icon : TextureRect = %Icon +@onready var label : Label = %Label + +func setup(hint : Hint) -> void: + var tex := ControllerIconTexture.new() + tex.path = String(hint.action) + icon.texture = tex + label.text = tr(hint.label) diff --git a/game/ui/hints/hint_row.gd.uid b/game/ui/hints/hint_row.gd.uid new file mode 100644 index 0000000..4f57789 --- /dev/null +++ b/game/ui/hints/hint_row.gd.uid @@ -0,0 +1 @@ +uid://bul0y1cx8h6m3 diff --git a/game/ui/hints/hint_row.tscn b/game/ui/hints/hint_row.tscn new file mode 100644 index 0000000..6563c62 --- /dev/null +++ b/game/ui/hints/hint_row.tscn @@ -0,0 +1,18 @@ +[gd_scene format=3 uid="uid://dfxmt1caqfh1o"] + +[ext_resource type="Script" uid="uid://bul0y1cx8h6m3" path="res://game/ui/hints/hint_row.gd" id="1_ra04h"] + +[node name="HintRow" type="HBoxContainer" unique_id=342863396] +theme_override_constants/separation = 6 +script = ExtResource("1_ra04h") + +[node name="Icon" type="TextureRect" parent="." unique_id=273257005] +unique_name_in_owner = true +custom_minimum_size = Vector2(40, 40) +layout_mode = 2 +expand_mode = 1 +stretch_mode = 5 + +[node name="Label" type="Label" parent="." unique_id=1767574240] +unique_name_in_owner = true +layout_mode = 2 diff --git a/game/ui/hints/hints.gd b/game/ui/hints/hints.gd index fe2b4a1..585bc89 100644 --- a/game/ui/hints/hints.gd +++ b/game/ui/hints/hints.gd @@ -2,57 +2,40 @@ extends Node signal changed -#catalogue unique : action -> libellé affiché -const LABELS : Dictionary = { - &"player_jump": "Sauter", - &"player_dismount": "Descendre", - &"player_aim_switch_target": "Changer de cible", - &"player_aim_unlock": "Libérer la cible", - &"player_throw_cancel": "Annuler le lancer", -} +var _layers : Dictionary = {} #source (objet ou StringName) -> Array[Hint] -var _state_actions : Array = [] # [action, ...] -var _context_hints : Dictionary = {} # clé (objet ou StringName) -> {action, text, priority} - -func label_for(action : StringName) -> String: - return LABELS.get(action, String(action)) #sans entrée : le nom de l'action, bien visible - -#---------- hints d'état (appelés par la StateMachine) ---------- - -func set_state_hints(actions : Array) -> void: - _state_actions = actions +func show_hints(source : Variant, list : Array) -> void: + var clean : Array = list.filter(func(h) -> bool: return h != null) #un Hint non assigne est ignore + if clean.is_empty(): + clear_hints(source) + return + if _layers.get(source) == clean: return + _layers[source] = clean changed.emit() -#---------- hints de contexte (monture, PNJ, etc.) ---------- -#text vide : le libellé vient du catalogue. À renseigner quand le sens dépend du contexte (Grimper / Parler sur la même action) +func clear_hints(source : Variant) -> void: + if _layers.erase(source): changed.emit() -func set_context(source : Variant, action : StringName, text : String = "", priority : int = 0) -> void: - var h : Dictionary = {"action": action, "text": text if text != "" else label_for(action), "priority": priority} - if _context_hints.get(source) == h: return - _context_hints[source] = h - changed.emit() - -func clear_context(source : Variant) -> void: - if _context_hints.erase(source): changed.emit() - -#---------- lecture (par le HUD) ---------- - -func get_visible() -> Array: - for key in _context_hints.keys(): - if typeof(key) == TYPE_OBJECT and !is_instance_valid(key): _context_hints.erase(key) +func get_visible() -> Array[Hint]: + #aplatit toutes les sources, en gardant l'ordre d'arrivee pour departager les priorites egales + var entries : Array = [] #[hint, ordre] + for source in _layers.keys(): + if typeof(source) == TYPE_OBJECT and !is_instance_valid(source): + _layers.erase(source) #source detruite sans avoir appele clear_hints + continue + for h in _layers[source]: + entries.append([h, entries.size()]) - var ctx : Array = _context_hints.values() - ctx.sort_custom(func(a, b): return a.priority > b.priority) + entries.sort_custom(func(a : Array, b : Array) -> bool: + if a[0].priority != b[0].priority: return a[0].priority > b[0].priority + return a[1] < b[1]) - #le contexte passe avant l'état ; une même action n'apparaît qu'une fois - var result : Array = [] + #une action = un seul hint (le plus prioritaire) + var result : Array[Hint] = [] var used : Dictionary = {} - for h in ctx: + for e in entries: + var h : Hint = e[0] if used.has(h.action): continue used[h.action] = true result.append(h) - for action in _state_actions: - if used.has(action): continue - used[action] = true - result.append({"action": action, "text": label_for(action)}) return result diff --git a/game/ui/hints/hints_hud.gd b/game/ui/hints/hints_hud.gd index 4a0263f..f1e6969 100644 --- a/game/ui/hints/hints_hud.gd +++ b/game/ui/hints/hints_hud.gd @@ -1,33 +1,27 @@ extends MarginContainer +const ROW_SCENE : PackedScene = preload("res://game/ui/hints/hint_row.tscn") + @onready var rows : HBoxContainer = $HBoxContainer +var _refresh_queued : bool = false + func _ready() -> void: - Hints.changed.connect(refresh) + Hints.changed.connect(_queue_refresh) refresh() +func _queue_refresh() -> void: + #plusieurs changements dans la meme frame (transition d'etat + monture...) : une seule reconstruction + if _refresh_queued: return + _refresh_queued = true + refresh.call_deferred() + func refresh() -> void: + _refresh_queued = false for c in rows.get_children(): rows.remove_child(c) c.queue_free() - for h in Hints.get_visible(): - rows.add_child(make_row(h.action, h.text)) - -func make_row(action : StringName, text : String) -> HBoxContainer: - var row := HBoxContainer.new() - - var tex := ControllerIconTexture.new() - tex.path = String(action) #même chose que le champ "path" dans ta scène - - var icon := TextureRect.new() - icon.texture = tex - icon.custom_minimum_size = Vector2(40, 40) - icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE - icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED - row.add_child(icon) - - var label := Label.new() - label.text = text - label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - row.add_child(label) - return row + for hint in Hints.get_visible(): + var row : HintRow = ROW_SCENE.instantiate() + rows.add_child(row) + row.setup(hint)