feat(gameplay): consommables utilisables avec touche [F]

- DolphinController expose heal/feed/refill_oxygen + signal
  use_consumable_requested (bindé sur F)
- Main.gd table CONSUMABLE_EFFECTS:
  · Bulle d'air (102): +40 O₂
  · Algue cuisinée (103): +30 faim, +5 HP
  · Amulette de soin (106): +50 HP
- Popup flottant coloré au-dessus du joueur + son bulle à l'utilisation
- HUD: hint dynamique "[F] Consommer : <item>" quand slot sélectionné = consommable

Boucle court-terme: le craft a enfin un usage direct, récompense lisible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 18:12:56 +00:00
parent 7f6811995d
commit 27459e1eaa
3 changed files with 100 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ signal hotbar_scroll(direction: int)
signal echolocation_triggered(position: Vector3, radius: float)
signal stats_changed(oxygen: float, health: float, hunger: float)
signal player_died()
signal use_consumable_requested()
# --- Internal State ---
var oxygen: float
@@ -61,6 +62,7 @@ func _setup_input_actions() -> void:
"boost": KEY_CTRL,
"echolocate": KEY_E,
"toggle_inventory": KEY_TAB,
"use_consumable": KEY_F,
}
for action_name: String in actions:
if not InputMap.has_action(action_name):
@@ -103,6 +105,8 @@ func _input(event: InputEvent) -> void:
_do_raycast_place()
if event.is_action_pressed("echolocate"):
_trigger_echolocation()
if event.is_action_pressed("use_consumable"):
use_consumable_requested.emit()
func _physics_process(delta: float) -> void:
@@ -264,6 +268,21 @@ func take_damage(amount: float) -> void:
player_died.emit()
func heal(amount: float) -> void:
health = min(max_health, health + amount)
_emit_stats()
func feed(amount: float) -> void:
hunger = min(max_hunger, hunger + amount)
_emit_stats()
func refill_oxygen(amount: float) -> void:
oxygen = min(max_oxygen, oxygen + amount)
_emit_stats()
func _trigger_echolocation() -> void:
if is_echolocating:
return

View File

@@ -52,6 +52,7 @@ func _ready() -> void:
qm.quest_completed.connect(_on_quest_completed)
_on_quests_updated()
_build_toast_container()
_build_consumable_hint()
var am: Node = get_node_or_null("/root/AchievementManager")
if am != null:
am.achievement_unlocked.connect(_on_achievement_unlocked)
@@ -239,6 +240,46 @@ func _on_quest_completed(q: Dictionary) -> void:
am.call("play_bubble_sfx", _dolphin.global_position)
var _consumable_hint: Label = null
func _build_consumable_hint() -> void:
_consumable_hint = Label.new()
_consumable_hint.anchor_left = 0.5
_consumable_hint.anchor_right = 0.5
_consumable_hint.anchor_top = 1.0
_consumable_hint.anchor_bottom = 1.0
_consumable_hint.offset_left = -220.0
_consumable_hint.offset_right = 220.0
_consumable_hint.offset_top = -105.0
_consumable_hint.offset_bottom = -85.0
_consumable_hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_consumable_hint.add_theme_font_size_override("font_size", 11)
_consumable_hint.add_theme_color_override("font_color", Color(0.85, 0.95, 1.0, 0.9))
_consumable_hint.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.85))
_consumable_hint.add_theme_constant_override("outline_size", 3)
_consumable_hint.visible = false
add_child(_consumable_hint)
func _update_consumable_hint() -> void:
if _consumable_hint == null or not is_instance_valid(_dolphin):
return
var main: Node = get_tree().get_first_node_in_group("main")
if main == null or not ("inventory" in main):
_consumable_hint.visible = false
return
var slot: Variant = main.inventory.get_selected_item()
if slot == null:
_consumable_hint.visible = false
return
if not ItemDatabase.is_consumable(slot["item_id"]):
_consumable_hint.visible = false
return
_consumable_hint.text = "[F] Consommer : %s" % ItemDatabase.get_item_name(slot["item_id"])
_consumable_hint.visible = true
func _build_toast_container() -> void:
_toast_container = VBoxContainer.new()
_toast_container.anchor_left = 0.5
@@ -413,6 +454,7 @@ func _process(delta: float) -> void:
_quest_complete_banner.modulate.a = clampf(_quest_banner_timer / 0.6, 0.0, 1.0)
_update_toasts(delta)
_update_consumable_hint()
if not is_instance_valid(_dolphin):
return