extends CanvasLayer # Autoload : DebugPokemonPanel #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 _buttons : Array[Button] = [] var _populated : bool = false #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(): queue_free() return layer = 100 process_mode = Node.PROCESS_MODE_ALWAYS #le panneau reste utilisable pendant que le jeu est en pause _setup_input() _build_ui() _root.visible = false func _setup_input() -> void: if InputMap.has_action(TOGGLE_ACTION): return InputMap.add_action(TOGGLE_ACTION) var key := InputEventKey.new() key.physical_keycode = KEY_F3 InputMap.action_add_event(TOGGLE_ACTION, key) var pad := InputEventJoypadButton.new() pad.button_index = JOY_BUTTON_BACK InputMap.action_add_event(TOGGLE_ACTION, pad) func _unhandled_input(event : InputEvent) -> void: if _root == null: return if event.is_action_pressed(TOGGLE_ACTION): if _root.visible: _close() else: _open() get_viewport().set_input_as_handled() elif _root.visible and event.is_action_pressed(&"ui_cancel"): #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 _mode == Mode.ADD and !_populated: _populate() _was_paused = get_tree().paused _prev_mouse_mode = Input.mouse_mode get_tree().paused = true Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _root.visible = true _message.text = "" _refresh_team() if _mode == Mode.MOVES: _refresh_moves_team() _focus_first.call_deferred() func _close() -> void: _root.visible = false get_tree().paused = _was_paused Input.mouse_mode = _prev_mouse_mode func _focus_first() -> void: 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 ---------- func _build_ui() -> void: _root = Control.new() _root.set_anchors_preset(Control.PRESET_FULL_RECT) add_child(_root) var dim := ColorRect.new() dim.color = Color(0.0, 0.0, 0.0, 0.6) dim.set_anchors_preset(Control.PRESET_FULL_RECT) _root.add_child(dim) var center := CenterContainer.new() center.set_anchors_preset(Control.PRESET_FULL_RECT) _root.add_child(center) var panel := PanelContainer.new() panel.custom_minimum_size = Vector2(620, 680) center.add_child(panel) var margin := MarginContainer.new() for side in ["left", "right", "top", "bottom"]: margin.add_theme_constant_override("margin_" + side, 16) panel.add_child(margin) var box := VBoxContainer.new() box.add_theme_constant_override("separation", 8) margin.add_child(box) var title := Label.new() 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() view.add_child(level_row) var level_label := Label.new() level_label.text = "Niveau" level_row.add_child(level_label) _level_spin = SpinBox.new() _level_spin.min_value = 1 _level_spin.max_value = PokemonInstance.MAX_LEVEL _level_spin.step = 1 _level_spin.value = DEFAULT_LEVEL level_row.add_child(_level_spin) _filter = LineEdit.new() _filter.placeholder_text = "Rechercher (nom, type, id)..." _filter.clear_button_enabled = true _filter.size_flags_horizontal = Control.SIZE_EXPAND_FILL _filter.text_changed.connect(_on_filter_changed) level_row.add_child(_filter) _team_label = Label.new() _team_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART 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 view.add_child(_scroll) _list = VBoxContainer.new() _list.size_flags_horizontal = Control.SIZE_EXPAND_FILL _scroll.add_child(_list) 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 for id in Pokedex.all_ids(): var data : PokemonData = Pokedex.get_pokemon(id) if data == null: continue var types_text : String = ", ".join(data.types.filter(func(t : TypeData) -> bool: return t != null).map(func(t : TypeData) -> String: return t.get_display_name())) var button := Button.new() button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.text = "#%d %s (%s)" % [data.id, data.get_display_name(), types_text] button.icon = data.menu_sprite button.expand_icon = true button.add_theme_constant_override("icon_max_width", 32) button.set_meta("search", ("%d %s %s %s" % [data.id, data.api_name, data.get_display_name(), types_text]).to_lower()) button.pressed.connect(_on_pokemon_pressed.bind(data)) _list.add_child(button) _buttons.append(button) if _buttons.is_empty(): _message.text = "Aucun Pokémon dans le Pokedex." func _on_pokemon_pressed(data : PokemonData) -> void: var team : PokemonTeam = GameData.player_team if team.is_full(): _message.text = "Équipe pleine (%d/%d)" % [team.size(), PokemonTeam.MAX_SIZE] return var pokemon := PokemonInstance.create(data, int(_level_spin.value)) GameData.give_pokemon(pokemon) _message.text = "%s Lv.%d ajouté (%d/%d)" % [pokemon.get_display_name(), pokemon.level, team.size(), PokemonTeam.MAX_SIZE] _refresh_team() func _on_filter_changed(text : String) -> void: var needle : String = text.strip_edges().to_lower() for b in _buttons: b.visible = needle == "" or needle in String(b.get_meta("search")) func _refresh_team() -> void: var team : PokemonTeam = GameData.player_team if team.is_empty(): _team_label.text = "Équipe : (vide)" return var names : Array[String] = [] 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()