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:
@@ -183,6 +183,8 @@ func _connect_dolphin_signals(dolphin: CharacterBody3D) -> void:
|
||||
dolphin.echolocation_triggered.connect(_on_echolocation)
|
||||
if dolphin.has_signal("hotbar_scroll"):
|
||||
dolphin.hotbar_scroll.connect(inventory.scroll_hotbar)
|
||||
if dolphin.has_signal("use_consumable_requested"):
|
||||
dolphin.use_consumable_requested.connect(_on_use_consumable)
|
||||
|
||||
|
||||
func _on_net_peer_connected(peer_id: int) -> void:
|
||||
@@ -290,6 +292,43 @@ func _on_block_place(hit_position: Vector3, normal: Vector3) -> void:
|
||||
inventory.remove_item_from_slot(inventory.selected_hotbar, 1)
|
||||
|
||||
|
||||
const CONSUMABLE_EFFECTS: Dictionary = {
|
||||
102: {"oxygen": 40.0, "hp": 0.0, "hunger": 0.0, "label": "+40 O₂", "color": Color(0.3, 0.9, 1.0)},
|
||||
103: {"oxygen": 0.0, "hp": 5.0, "hunger": 30.0, "label": "+30 🍴", "color": Color(0.6, 1.0, 0.4)},
|
||||
106: {"oxygen": 0.0, "hp": 50.0, "hunger": 0.0, "label": "+50 ❤", "color": Color(1.0, 0.5, 0.5)},
|
||||
}
|
||||
|
||||
|
||||
func _on_use_consumable() -> void:
|
||||
if not is_instance_valid(_my_dolphin):
|
||||
return
|
||||
var slot: Variant = inventory.get_selected_item()
|
||||
if slot == null:
|
||||
return
|
||||
var item_id: int = slot["item_id"]
|
||||
if not CONSUMABLE_EFFECTS.has(item_id):
|
||||
return
|
||||
var effect: Dictionary = CONSUMABLE_EFFECTS[item_id]
|
||||
if effect["oxygen"] > 0.0 and _my_dolphin.has_method("refill_oxygen"):
|
||||
_my_dolphin.refill_oxygen(effect["oxygen"])
|
||||
if effect["hp"] > 0.0 and _my_dolphin.has_method("heal"):
|
||||
_my_dolphin.heal(effect["hp"])
|
||||
if effect["hunger"] > 0.0 and _my_dolphin.has_method("feed"):
|
||||
_my_dolphin.feed(effect["hunger"])
|
||||
inventory.remove_item_from_slot(inventory.selected_hotbar, 1)
|
||||
AudioManager.play_bubble_sfx(_my_dolphin.global_position)
|
||||
_spawn_consumable_popup(effect["label"], effect["color"])
|
||||
|
||||
|
||||
func _spawn_consumable_popup(label: String, color: Color) -> void:
|
||||
if not is_instance_valid(_my_dolphin):
|
||||
return
|
||||
var popup_script: Script = load("res://scripts/progression/XpPopup.gd")
|
||||
if popup_script == null:
|
||||
return
|
||||
popup_script.spawn(self, label, _my_dolphin.global_position + Vector3(0, 1.4, 0), color)
|
||||
|
||||
|
||||
func _on_echolocation(position: Vector3, _radius: float) -> void:
|
||||
AudioManager.play_bubble_sfx(position)
|
||||
var am: Node = get_node_or_null("/root/AchievementManager")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user