Start quest journal UI

This commit is contained in:
TheThomaas 2026-10-08 17:42:14 +02:00
parent ffa986afe4
commit beaffbf990
15 changed files with 429 additions and 1 deletions

View file

@ -0,0 +1,28 @@
extends HBoxContainer
@onready var check_icon: Label = $CheckIcon
@onready var objective_label: Label = $ObjectiveLabel
func setup(obj: QuestObjective, is_quest_completed: bool) -> void:
# Texte : description + compteur si nécessaire
if obj.required_count > 1:
objective_label.text = "%s (%d/%d)" % [
obj.description, obj.current_count, obj.required_count
]
else:
objective_label.text = obj.description
# Petit marqueur "optionnel"
if obj.is_optional:
objective_label.text += " [optionnel]"
# Icône et couleur selon l'état
var complete := obj.is_complete() or is_quest_completed
if complete:
check_icon.text = "☑"
check_icon.add_theme_color_override("font_color", Color(0.4, 0.9, 0.4))
objective_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6))
else:
check_icon.text = "☐"
check_icon.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8))
objective_label.add_theme_color_override("font_color", Color(1, 1, 1))

View file

@ -0,0 +1 @@
uid://b1uu6845ugtwd

View file

@ -0,0 +1,15 @@
[gd_scene format=3 uid="uid://bmi4nup81860h"]
[ext_resource type="Script" uid="uid://b1uu6845ugtwd" path="res://game/quest_journal/objective_row.gd" id="1_0bes5"]
[node name="ObjectiveRow" type="HBoxContainer" unique_id=208897443]
script = ExtResource("1_0bes5")
[node name="CheckIcon" type="Label" parent="." unique_id=264135563]
custom_minimum_size = Vector2(24, 24)
custom_maximum_size = Vector2(24, 24)
layout_mode = 2
[node name="ObjectiveLabel" type="Label" parent="." unique_id=1304877640]
layout_mode = 2
size_flags_horizontal = 3

View file

@ -0,0 +1,30 @@
extends Button
class_name QuestEntry
signal quest_selected(quest_id: StringName)
@onready var title_label: Label = $HBoxContainer/VBoxContainer/TitleLabel
@onready var progress_label: Label = $HBoxContainer/VBoxContainer/ProgressLabel
@onready var status_icon: Label = $HBoxContainer/StatusIcon
const QuestData = preload("uid://bldyeahkrms8n")
var _quest_id: StringName = &""
func setup(quest: QuestData, is_completed: bool) -> void:
_quest_id = quest.quest_id
title_label.text = quest.title
if is_completed:
progress_label.text = "Terminée"
status_icon.text = "✓"
status_icon.add_theme_color_override("font_color", Color(0.4, 0.9, 0.4))
else:
var total := quest.get_required_objectives().size()
var done := 0
for obj in quest.get_required_objectives():
if obj.is_complete(): done += 1
progress_label.text = "%d/%d objectifs" % [done, total]
status_icon.text = "▶" if done < total else "✓"
status_icon.add_theme_color_override("font_color", Color(0.9, 0.8, 0.4))
pressed.connect(func(): quest_selected.emit(_quest_id))

View file

@ -0,0 +1 @@
uid://hejqtr201qcw

View file

@ -0,0 +1,29 @@
[gd_scene format=3 uid="uid://cssst3sesinb"]
[ext_resource type="Script" uid="uid://hejqtr201qcw" path="res://game/quest_journal/quest_entry.gd" id="1_xva0s"]
[sub_resource type="LabelSettings" id="LabelSettings_3agn1"]
font_size = 12
[node name="QuestEntry" type="Button" unique_id=1674293762]
flat = true
script = ExtResource("1_xva0s")
[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=122221174]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer" unique_id=39281024]
layout_mode = 2
size_flags_horizontal = 3
[node name="TitleLabel" type="Label" parent="HBoxContainer/VBoxContainer" unique_id=798269508]
layout_mode = 2
[node name="ProgressLabel" type="Label" parent="HBoxContainer/VBoxContainer" unique_id=1640888083]
layout_mode = 2
label_settings = SubResource("LabelSettings_3agn1")
[node name="StatusIcon" type="Label" parent="HBoxContainer" unique_id=1154347679]
layout_mode = 2

View file

@ -0,0 +1,154 @@
extends Control
class_name QuestJournal
enum Tab { MAIN, SIDE, COMPLETED }
const QUEST_ENTRY_SCENE := preload("res://game/quest_journal/quest_entry.tscn")
const OBJECTIVE_ROW_SCENE := preload("res://game/quest_journal/objective_row.tscn")
const REWARD_ROW_SCENE := preload("res://game/quest_journal/reward_row.tscn")
@onready var tab_bar: TabBar = $MarginContainer/HBoxContainer/LeftPanel/TabBar
@onready var quest_list: VBoxContainer = $MarginContainer/HBoxContainer/LeftPanel/ScrollContainer/QuestList
@onready var footer_label: Label = $MarginContainer/HBoxContainer/LeftPanel/FooterLabel
@onready var detail_title: Label = $MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent/DetailTitle
@onready var detail_category: Label = $MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent/DetailCategory
@onready var detail_description: RichTextLabel = $MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent/DetailDescription
@onready var objectives_list: VBoxContainer = $MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent/ObjectivesList
@onready var rewards_list: VBoxContainer = $MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent/RewardsList
var _current_tab: Tab = Tab.MAIN
var _selected_quest_id: StringName = &""
func _ready() -> void:
tab_bar.tab_changed.connect(_on_tab_changed)
GameEvents.quest_started.connect(_on_quest_changed)
GameEvents.quest_completed.connect(_on_quest_changed)
GameEvents.quest_objective_updated.connect(_on_objective_updated)
refresh()
func _on_tab_changed(index: int) -> void:
_current_tab = index as Tab
refresh()
func _on_quest_changed(_id: StringName) -> void:
refresh()
func _on_objective_updated(_qid: StringName, _idx: int, _cur: int, _req: int) -> void:
refresh() # simple et suffisant
# --- Rafraîchissement global ---
func refresh() -> void:
_clear_list()
var quests := _get_quests_for_tab(_current_tab)
if quests.is_empty():
_show_empty_state()
_clear_detail()
return
# Tri : principales par chapter_index, secondaires par titre
if _current_tab == Tab.MAIN:
quests.sort_custom(func(a, b): return a.chapter_index < b.chapter_index)
else:
quests.sort_custom(func(a, b): return a.title < b.title)
for quest in quests:
var entry := QUEST_ENTRY_SCENE.instantiate()
entry.setup(quest, _current_tab == Tab.COMPLETED)
entry.quest_selected.connect(_on_quest_selected)
quest_list.add_child(entry)
# Auto-sélection : garde la sélection actuelle si elle est toujours valide
var still_valid := quests.any(func(q): return q.quest_id == _selected_quest_id)
if still_valid:
_on_quest_selected(_selected_quest_id)
elif quests.size() > 0:
_on_quest_selected(quests[0].quest_id)
else:
_clear_detail()
_update_footer()
func _get_quests_for_tab(tab: Tab) -> Array[QuestData]:
var result: Array[QuestData] = []
match tab:
Tab.MAIN:
for q in QuestManager.active_quests.values():
if q.category == QuestData.Category.MAIN:
result.append(q)
Tab.SIDE:
for q in QuestManager.active_quests.values():
if q.category == QuestData.Category.SIDE:
result.append(q)
Tab.COMPLETED:
for q in QuestManager.completed_quests.values():
result.append(q)
return result
func _clear_list() -> void:
for child in quest_list.get_children():
child.queue_free()
func _show_empty_state() -> void:
var label := Label.new()
label.text = "Aucune quête dans cette catégorie."
label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6))
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
quest_list.add_child(label)
func _update_footer() -> void:
var main_count := 0
var side_count := 0
for q in QuestManager.active_quests.values():
if q.category == QuestData.Category.MAIN: main_count += 1
elif q.category == QuestData.Category.SIDE: side_count += 1
footer_label.text = "Principales : %d | Secondaires : %d | Terminées : %d" % [
main_count, side_count, QuestManager.completed_quests.size()
]
# --- Détail d'une quête ---
func _on_quest_selected(quest_id: StringName) -> void:
_selected_quest_id = quest_id
var quest: QuestData = QuestManager.all_quests.get(quest_id)
if quest == null:
_clear_detail()
return
detail_title.text = quest.title
detail_category.text = _category_label(quest.category)
detail_description.text = quest.description
# Objectifs
for child in objectives_list.get_children():
child.queue_free()
for obj in quest.objectives:
var row := OBJECTIVE_ROW_SCENE.instantiate()
row.setup(obj, _current_tab == Tab.COMPLETED)
objectives_list.add_child(row)
# Récompenses
for child in rewards_list.get_children():
child.queue_free()
for reward in quest.rewards:
var row := REWARD_ROW_SCENE.instantiate()
row.setup(reward)
rewards_list.add_child(row)
func _clear_detail() -> void:
detail_title.text = ""
detail_category.text = ""
detail_description.text = ""
for child in objectives_list.get_children():
child.queue_free()
for child in rewards_list.get_children():
child.queue_free()
func _category_label(cat: QuestData.Category) -> String:
match cat:
QuestData.Category.MAIN: return "QUÊTE PRINCIPALE"
QuestData.Category.SIDE: return "QUÊTE SECONDAIRE"
QuestData.Category.HIDDEN: return "QUÊTE SECRÈTE"
return ""

View file

@ -0,0 +1 @@
uid://b3vt467ykvg8u

View file

@ -0,0 +1,105 @@
[gd_scene format=3 uid="uid://cluiqkghowxdw"]
[ext_resource type="Script" uid="uid://b3vt467ykvg8u" path="res://game/quest_journal/quest_journal.gd" id="1_ri51t"]
[sub_resource type="LabelSettings" id="LabelSettings_3agn1"]
font_size = 12
font_color = Color(0.8051242, 0.8051241, 7.70092e-07, 1)
[node name="QuestJournal" type="Control" unique_id=625487910]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_ri51t")
[node name="Background" type="ColorRect" parent="." unique_id=530768479]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0.42745098)
[node name="MarginContainer" type="MarginContainer" parent="." unique_id=1311181226]
process_mode = 3
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 40
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 40
theme_override_constants/margin_bottom = 40
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer" unique_id=1393741966]
layout_mode = 2
[node name="LeftPanel" type="VBoxContainer" parent="MarginContainer/HBoxContainer" unique_id=422872187]
custom_minimum_size = Vector2(380, 0)
custom_maximum_size = Vector2(380, -1)
layout_mode = 2
[node name="TitleLabel" type="Label" parent="MarginContainer/HBoxContainer/LeftPanel" unique_id=510808795]
layout_mode = 2
text = "JOURNAL DE QUÊTES"
[node name="TabBar" type="TabBar" parent="MarginContainer/HBoxContainer/LeftPanel" unique_id=1903671008]
layout_mode = 2
current_tab = 0
tab_count = 3
tab_0/title = "Principales"
tab_1/title = "Secondaires"
tab_2/title = "Terminées"
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/HBoxContainer/LeftPanel" unique_id=678478455]
layout_mode = 2
size_flags_vertical = 3
[node name="QuestList" type="VBoxContainer" parent="MarginContainer/HBoxContainer/LeftPanel/ScrollContainer" unique_id=1539103998]
layout_mode = 2
[node name="FooterLabel" type="Label" parent="MarginContainer/HBoxContainer/LeftPanel" unique_id=1538203911]
layout_mode = 2
[node name="RightPanel" type="PanelContainer" parent="MarginContainer/HBoxContainer" unique_id=614836913]
layout_mode = 2
size_flags_horizontal = 3
[node name="DetailScroll" type="ScrollContainer" parent="MarginContainer/HBoxContainer/RightPanel" unique_id=195451550]
layout_mode = 2
[node name="DetailContent" type="VBoxContainer" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll" unique_id=2096180388]
layout_mode = 2
[node name="DetailTitle" type="Label" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent" unique_id=2026354509]
layout_mode = 2
[node name="DetailCategory" type="Label" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent" unique_id=1664568446]
layout_mode = 2
label_settings = SubResource("LabelSettings_3agn1")
[node name="HSeparator" type="HSeparator" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent" unique_id=21965256]
layout_mode = 2
[node name="DetailDescription" type="RichTextLabel" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent" unique_id=1262773543]
layout_mode = 2
[node name="ObjectivesTitle" type="Label" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent" unique_id=510015609]
layout_mode = 2
text = "OBJECTIFS"
[node name="ObjectivesList" type="VBoxContainer" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent" unique_id=954789404]
layout_mode = 2
[node name="RewardsTitle" type="Label" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent" unique_id=767078748]
layout_mode = 2
text = "RÉCOMPENSES"
[node name="RewardsList" type="VBoxContainer" parent="MarginContainer/HBoxContainer/RightPanel/DetailScroll/DetailContent" unique_id=851905982]
layout_mode = 2

View file

@ -0,0 +1,20 @@
extends HBoxContainer
@onready var icon_label: Label = $IconLabel
@onready var reward_label: Label = $RewardLabel
func setup(reward: QuestReward) -> void:
match reward.type:
QuestReward.Type.ITEM:
icon_label.text = "🎁"
reward_label.text = "%s × %d" % [reward.target_id, reward.amount]
QuestReward.Type.MONEY:
icon_label.text = "💰"
reward_label.text = "%d pièces" % reward.amount
QuestReward.Type.POKEMON:
icon_label.text = "🐾"
reward_label.text = "Pokémon : %s" % reward.target_id
QuestReward.Type.UNLOCK_QUEST:
icon_label.text = "🔓"
reward_label.text = "Nouvelle quête : %s" % reward.target_id
icon_label.add_theme_color_override("font_color", Color(0.9, 0.8, 0.4))

View file

@ -0,0 +1 @@
uid://b0m0dg8eccu4u

View file

@ -0,0 +1,15 @@
[gd_scene format=3 uid="uid://bhyrhg21amey2"]
[ext_resource type="Script" uid="uid://b0m0dg8eccu4u" path="res://game/quest_journal/reward_row.gd" id="1_qcyvc"]
[node name="RewardRow" type="HBoxContainer" unique_id=1679893101]
script = ExtResource("1_qcyvc")
[node name="IconLabel" type="Label" parent="." unique_id=2121958636]
custom_minimum_size = Vector2(24, 24)
custom_maximum_size = Vector2(24, 24)
layout_mode = 2
[node name="RewardLabel" type="Label" parent="." unique_id=513948004]
layout_mode = 2
size_flags_horizontal = 3

View file

@ -23,6 +23,10 @@ func _ready() -> void:
animation_player.play("RESET") animation_player.play("RESET")
func _unhandled_input(event: InputEvent) -> void: func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("open_quest_journal"):
toggle_journal()
get_viewport().set_input_as_handled()
if is_transitioning or sub_screen != null: return #une page ouverte gere ses propres entrees if is_transitioning or sub_screen != null: return #une page ouverte gere ses propres entrees
if event.is_action_pressed("pause"): if event.is_action_pressed("pause"):
if get_tree().paused: if get_tree().paused:
@ -123,3 +127,23 @@ func _on_restart_pressed() -> void:
func _on_quit_pressed() -> void: func _on_quit_pressed() -> void:
UiAudio.play(click_sound) UiAudio.play(click_sound)
get_tree().quit() get_tree().quit()
const QUEST_JOURNAL_SCENE := preload("res://game/quest_journal/quest_journal.tscn")
var _journal_instance: Control = null
func toggle_journal() -> void:
if _journal_instance and is_instance_valid(_journal_instance):
_journal_instance.queue_free()
_journal_instance = null
get_tree().paused = false
else:
_journal_instance = QUEST_JOURNAL_SCENE.instantiate()
add_child(_journal_instance)
get_tree().paused = true
func _on_quests_pressed() -> void:
toggle_journal()

View file

@ -205,7 +205,6 @@ libraries/ = SubResource("AnimationLibrary_2nc7s")
[connection signal="pressed" from="PanelContainer/VBoxContainer/Resume" to="." method="_on_resume_pressed"] [connection signal="pressed" from="PanelContainer/VBoxContainer/Resume" to="." method="_on_resume_pressed"]
[connection signal="pressed" from="PanelContainer/VBoxContainer/Team" to="." method="_on_team_pressed"] [connection signal="pressed" from="PanelContainer/VBoxContainer/Team" to="." method="_on_team_pressed"]
[connection signal="pressed" from="PanelContainer/VBoxContainer/Team" to="." method="_on_team_pressed"]
[connection signal="pressed" from="PanelContainer/VBoxContainer/Storage" to="." method="_on_storage_pressed"] [connection signal="pressed" from="PanelContainer/VBoxContainer/Storage" to="." method="_on_storage_pressed"]
[connection signal="pressed" from="PanelContainer/VBoxContainer/Save" to="." method="_on_save_pressed"] [connection signal="pressed" from="PanelContainer/VBoxContainer/Save" to="." method="_on_save_pressed"]
[connection signal="pressed" from="PanelContainer/VBoxContainer/Load" to="." method="_on_load_pressed"] [connection signal="pressed" from="PanelContainer/VBoxContainer/Load" to="." method="_on_load_pressed"]

View file

@ -171,6 +171,11 @@ debug_quests={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null)
] ]
} }
open_quest_journal={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null)
]
}
[physics] [physics]