diff --git a/game/debug/debug_pokemon_panel.gd b/game/debug/debug_pokemon_panel.gd index 9171926..e0716d8 100644 --- a/game/debug/debug_pokemon_panel.gd +++ b/game/debug/debug_pokemon_panel.gd @@ -1,21 +1,43 @@ extends CanvasLayer # Autoload : DebugPokemonPanel -#panneau de test (build de debug uniquement) : liste des pokemon du Pokedex, un clic ajoute a l'equipe +#panneau de test (build de debug uniquement) : +# - onglet "Ajouter" : liste des pokemon du Pokedex, un clic ajoute a l'equipe +# - onglet "Capacites" : on choisit un pokemon de l'equipe, puis on remplace ses capacites +# par n'importe quelle capacite importee (recherche par nom, type, categorie ou statut) + +enum Mode { ADD, MOVES } const TOGGLE_ACTION : StringName = &"debug_pokemon_panel" const DEFAULT_LEVEL : int = 10 var _root : Control +var _mode : Mode = Mode.ADD +var _message : Label +var _was_paused : bool = false +var _prev_mouse_mode : Input.MouseMode = Input.MOUSE_MODE_VISIBLE + +#onglet "Ajouter" +var _add_view : Control var _filter : LineEdit var _level_spin : SpinBox var _team_label : Label var _scroll : ScrollContainer var _list : VBoxContainer -var _message : Label var _buttons : Array[Button] = [] var _populated : bool = false -var _was_paused : bool = false -var _prev_mouse_mode : Input.MouseMode = Input.MOUSE_MODE_VISIBLE + +#onglet "Capacites" +var _moves_view : Control +var _moves_team_box : HFlowContainer +var _slots_box : VBoxContainer +var _picker_box : VBoxContainer +var _picker_title : Label +var _move_filter : LineEdit +var _move_list : VBoxContainer +var _move_buttons : Array[Button] = [] +var _moves_populated : bool = false +var _edit_pokemon : PokemonInstance = null +var _edit_slot : int = -1 func _ready() -> void: if !OS.is_debug_build(): @@ -44,13 +66,15 @@ func _unhandled_input(event : InputEvent) -> void: else: _open() get_viewport().set_input_as_handled() elif _root.visible and event.is_action_pressed(&"ui_cancel"): - _close() + #Echap / B : ferme d'abord la liste des capacites, puis le panneau + if _mode == Mode.MOVES and _picker_box.visible: _close_picker() + else: _close() get_viewport().set_input_as_handled() #---------- ouverture / fermeture ---------- func _open() -> void: - if !_populated: _populate() + if _mode == Mode.ADD and !_populated: _populate() _was_paused = get_tree().paused _prev_mouse_mode = Input.mouse_mode get_tree().paused = true @@ -58,6 +82,7 @@ func _open() -> void: _root.visible = true _message.text = "" _refresh_team() + if _mode == Mode.MOVES: _refresh_moves_team() _focus_first.call_deferred() func _close() -> void: @@ -66,10 +91,29 @@ func _close() -> void: Input.mouse_mode = _prev_mouse_mode func _focus_first() -> void: - for b in _buttons: - if b.is_visible_in_tree(): - b.grab_focus() - return + if _mode == Mode.ADD: + for b in _buttons: + if b.is_visible_in_tree(): + b.grab_focus() + return + else: + for child in _moves_team_box.get_children(): + if child is Button: + child.grab_focus() + return + +func _set_mode(mode : Mode) -> void: + _mode = mode + _add_view.visible = mode == Mode.ADD + _moves_view.visible = mode == Mode.MOVES + _message.text = "" + if mode == Mode.ADD: + if !_populated: _populate() + _refresh_team() + else: + _close_picker() + _refresh_moves_team() + _focus_first.call_deferred() #---------- interface ---------- @@ -88,7 +132,7 @@ func _build_ui() -> void: _root.add_child(center) var panel := PanelContainer.new() - panel.custom_minimum_size = Vector2(580, 640) + panel.custom_minimum_size = Vector2(620, 680) center.add_child(panel) var margin := MarginContainer.new() @@ -101,12 +145,64 @@ func _build_ui() -> void: margin.add_child(box) var title := Label.new() - title.text = "Debug : ajouter un Pokémon à l'équipe" + title.text = "Debug : Pokémon" title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(title) + #onglets + var tabs := HBoxContainer.new() + tabs.add_theme_constant_override("separation", 8) + box.add_child(tabs) + var group := ButtonGroup.new() + tabs.add_child(_make_tab_button("Ajouter un Pokémon", Mode.ADD, group, true)) + tabs.add_child(_make_tab_button("Modifier les capacités", Mode.MOVES, group, false)) + + _build_add_view(box) + _build_moves_view(box) + + _message = Label.new() + _message.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + box.add_child(_message) + + var actions := HBoxContainer.new() + actions.add_theme_constant_override("separation", 8) + box.add_child(actions) + actions.add_child(_make_action_button("Soigner l'équipe", _on_heal_pressed)) + actions.add_child(_make_action_button("Vider l'équipe", _on_clear_pressed)) + actions.add_child(_make_action_button("Fermer", _close)) + + var hint := Label.new() + hint.text = "F3 / Select : ouvrir-fermer | Échap / B : retour, puis fermer" + hint.modulate = Color(1.0, 1.0, 1.0, 0.5) + hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + box.add_child(hint) + +func _make_tab_button(label : String, mode : Mode, group : ButtonGroup, pressed : bool) -> Button: + var button := Button.new() + button.text = label + button.toggle_mode = true + button.button_group = group + button.button_pressed = pressed + button.size_flags_horizontal = Control.SIZE_EXPAND_FILL + button.pressed.connect(_set_mode.bind(mode)) + return button + +func _make_action_button(label : String, callback : Callable) -> Button: + var button := Button.new() + button.text = label + button.size_flags_horizontal = Control.SIZE_EXPAND_FILL + button.pressed.connect(callback) + return button + +func _build_add_view(parent : Control) -> void: + var view := VBoxContainer.new() + view.size_flags_vertical = Control.SIZE_EXPAND_FILL + view.add_theme_constant_override("separation", 8) + parent.add_child(view) + _add_view = view + var level_row := HBoxContainer.new() - box.add_child(level_row) + view.add_child(level_row) var level_label := Label.new() level_label.text = "Niveau" level_row.add_child(level_label) @@ -125,40 +221,68 @@ func _build_ui() -> void: _team_label = Label.new() _team_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - box.add_child(_team_label) + view.add_child(_team_label) _scroll = ScrollContainer.new() _scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL _scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED _scroll.follow_focus = true - box.add_child(_scroll) + view.add_child(_scroll) _list = VBoxContainer.new() _list.size_flags_horizontal = Control.SIZE_EXPAND_FILL _scroll.add_child(_list) - - _message = Label.new() - box.add_child(_message) - - var actions := HBoxContainer.new() - actions.add_theme_constant_override("separation", 8) - box.add_child(actions) - actions.add_child(_make_action_button("Soigner l'équipe", _on_heal_pressed)) - actions.add_child(_make_action_button("Vider l'équipe", _on_clear_pressed)) - actions.add_child(_make_action_button("Fermer", _close)) - - var hint := Label.new() - hint.text = "F3 / Select : ouvrir-fermer | Échap / B : fermer" - hint.modulate = Color(1.0, 1.0, 1.0, 0.5) - hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - box.add_child(hint) -func _make_action_button(label : String, callback : Callable) -> Button: - var button := Button.new() - button.text = label - button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - button.pressed.connect(callback) - return button +func _build_moves_view(parent : Control) -> void: + var view := VBoxContainer.new() + view.size_flags_vertical = Control.SIZE_EXPAND_FILL + view.add_theme_constant_override("separation", 8) + view.visible = false + parent.add_child(view) + _moves_view = view + + var team_title := Label.new() + team_title.text = "Pokémon à modifier" + view.add_child(team_title) + _moves_team_box = HFlowContainer.new() + view.add_child(_moves_team_box) + + var slots_title := Label.new() + slots_title.text = "Capacités : clique sur un emplacement pour le remplacer" + view.add_child(slots_title) + _slots_box = VBoxContainer.new() + view.add_child(_slots_box) + + #liste de toutes les capacites (affichee quand un emplacement est choisi) + _picker_box = VBoxContainer.new() + _picker_box.size_flags_vertical = Control.SIZE_EXPAND_FILL + _picker_box.add_theme_constant_override("separation", 6) + _picker_box.visible = false + view.add_child(_picker_box) + + _picker_title = Label.new() + _picker_box.add_child(_picker_title) + + var search_row := HBoxContainer.new() + _picker_box.add_child(search_row) + _move_filter = LineEdit.new() + _move_filter.placeholder_text = "Rechercher (nom, type, physical/special/status, statut)..." + _move_filter.clear_button_enabled = true + _move_filter.size_flags_horizontal = Control.SIZE_EXPAND_FILL + _move_filter.text_changed.connect(_on_move_filter_changed) + search_row.add_child(_move_filter) + search_row.add_child(_make_action_button("Retirer cette capacité", _on_remove_move_pressed)) + + var move_scroll := ScrollContainer.new() + move_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + move_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + move_scroll.follow_focus = true + _picker_box.add_child(move_scroll) + _move_list = VBoxContainer.new() + _move_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL + move_scroll.add_child(_move_list) + +#---------- onglet "Ajouter" ---------- func _populate() -> void: _populated = true @@ -181,8 +305,6 @@ func _populate() -> void: if _buttons.is_empty(): _message.text = "Aucun Pokémon dans le Pokedex." -#---------- actions ---------- - func _on_pokemon_pressed(data : PokemonData) -> void: var team : PokemonTeam = GameData.player_team if team.is_full(): @@ -194,18 +316,6 @@ func _on_pokemon_pressed(data : PokemonData) -> void: _message.text = "%s Lv.%d ajouté (%d/%d)" % [pokemon.get_display_name(), pokemon.level, team.size(), PokemonTeam.MAX_SIZE] _refresh_team() -func _on_clear_pressed() -> void: - var team : PokemonTeam = GameData.player_team - while !team.is_empty(): - team.remove_at(team.size() - 1) - GameData.validate_selection() - _message.text = "Équipe vidée" - _refresh_team() - -func _on_heal_pressed() -> void: - GameData.player_team.heal_all() - _message.text = "Équipe soignée" - func _on_filter_changed(text : String) -> void: var needle : String = text.strip_edges().to_lower() for b in _buttons: @@ -220,3 +330,148 @@ func _refresh_team() -> void: for p in team.members: names.append("%s Lv.%d" % [p.get_display_name(), p.level]) _team_label.text = "Équipe (%d/%d) : %s" % [team.size(), PokemonTeam.MAX_SIZE, ", ".join(names)] + +#---------- onglet "Capacites" ---------- + +func _refresh_moves_team() -> void: + for child in _moves_team_box.get_children(): + _moves_team_box.remove_child(child) + child.queue_free() + + var team : PokemonTeam = GameData.player_team + if !team.members.has(_edit_pokemon): + _edit_pokemon = team.members[0] if !team.is_empty() else null + + var group := ButtonGroup.new() + for p in team.members: + var button := Button.new() + button.text = "%s Lv.%d" % [p.get_display_name(), p.level] + button.toggle_mode = true + button.button_group = group + button.button_pressed = p == _edit_pokemon + button.pressed.connect(_select_pokemon.bind(p)) + _moves_team_box.add_child(button) + + if team.is_empty(): + var empty := Label.new() + empty.text = "L'équipe est vide : ajoute d'abord un Pokémon (onglet « Ajouter »)." + _moves_team_box.add_child(empty) + _refresh_slots() + +func _select_pokemon(pokemon : PokemonInstance) -> void: + _edit_pokemon = pokemon + _close_picker() + _refresh_slots() + +func _refresh_slots() -> void: + for child in _slots_box.get_children(): + _slots_box.remove_child(child) + child.queue_free() + if _edit_pokemon == null: return + + for i in PokemonInstance.MAX_ACTIVE_MOVES: + var button := Button.new() + button.alignment = HORIZONTAL_ALIGNMENT_LEFT + var prefix : String = "▶ " if i == _edit_slot else "" + if i < _edit_pokemon.active_moves.size(): + var ms : MoveSlot = _edit_pokemon.active_moves[i] + button.text = "%s%d. %s PP %d/%d" % [prefix, i + 1, ms.move.get_display_name(), ms.current_pp, ms.max_pp] + else: + button.text = "%s%d. (vide)" % [prefix, i + 1] + button.pressed.connect(_on_slot_pressed.bind(i)) + _slots_box.add_child(button) + +func _on_slot_pressed(index : int) -> void: + if _edit_pokemon == null: return + _edit_slot = index + _picker_title.text = "Capacité n°%d de %s : choisis un remplacement" % [index + 1, _edit_pokemon.get_display_name()] + _picker_box.visible = true + _refresh_slots() + if !_moves_populated: await _populate_moves() + _move_filter.text = "" + _on_move_filter_changed("") + _focus_first_move.call_deferred() + +func _close_picker() -> void: + _edit_slot = -1 + if _picker_box != null: _picker_box.visible = false + if _slots_box != null: _refresh_slots() + +func _populate_moves() -> void: + #charge toutes les capacites importees (une seule fois) ; "Pokedex" les garde ensuite en cache + _moves_populated = true + var names : PackedStringArray = PackedStringArray() + for file in ResourceLoader.list_directory(PokeApiSettings.moves_dir()): + if file.ends_with(".tres"): names.append(file.get_basename()) + names.sort() + + var loaded : int = 0 + for move_name in names: + var move : MoveData = Pokedex.get_move(move_name) + if move == null: continue + + var power_text : String = str(move.power) if move.power > 0 else "-" + var button := Button.new() + button.alignment = HORIZONTAL_ALIGNMENT_LEFT + button.text = "%s [%s · %s · puiss. %s]" % [move.get_display_name(), move.type, move.category, power_text] + button.set_meta("search", ("%s %s %s %s %s" % [move_name, move.get_display_name(), move.type, move.category, move.ailment]).to_lower()) + button.pressed.connect(_on_move_pressed.bind(move)) + _move_list.add_child(button) + _move_buttons.append(button) + + loaded += 1 + if loaded % 40 == 0: + _message.text = "Chargement des capacités... %d/%d" % [loaded, names.size()] + await get_tree().process_frame + _message.text = "%d capacités disponibles." % loaded + if loaded == 0: + _message.text = "Aucune capacité trouvée : importe d'abord les capacités avec le plugin." + +func _on_move_filter_changed(text : String) -> void: + var needle : String = text.strip_edges().to_lower() + for b in _move_buttons: + b.visible = needle == "" or needle in String(b.get_meta("search")) + +func _focus_first_move() -> void: + for b in _move_buttons: + if b.is_visible_in_tree(): + b.grab_focus() + return + +func _on_move_pressed(move : MoveData) -> void: + if _edit_pokemon == null or _edit_slot < 0: return + var pokemon : PokemonInstance = _edit_pokemon + + #la capacite devient aussi "apprise" (coherence avec le reste du jeu) + if !pokemon.learned_moves.has(move): pokemon.learned_moves.append(move) + var move_slot := MoveSlot.new(move) + if _edit_slot < pokemon.active_moves.size(): + pokemon.active_moves[_edit_slot] = move_slot + else: + pokemon.active_moves.append(move_slot) + + _message.text = "%s : %s en position %d" % [pokemon.get_display_name(), move.get_display_name(), _edit_slot + 1] + _close_picker() + +func _on_remove_move_pressed() -> void: + if _edit_pokemon == null or _edit_slot < 0: return + if _edit_slot < _edit_pokemon.active_moves.size(): + _edit_pokemon.active_moves.remove_at(_edit_slot) + _message.text = "Capacité n°%d retirée" % (_edit_slot + 1) + _close_picker() + +#---------- actions communes ---------- + +func _on_clear_pressed() -> void: + var team : PokemonTeam = GameData.player_team + while !team.is_empty(): + team.remove_at(team.size() - 1) + GameData.validate_selection() + _message.text = "Équipe vidée" + _refresh_team() + if _mode == Mode.MOVES: _refresh_moves_team() + +func _on_heal_pressed() -> void: + GameData.player_team.heal_all() #rend aussi tous les PP + _message.text = "Équipe soignée (PV et PP)" + if _mode == Mode.MOVES: _refresh_slots() \ No newline at end of file