feat: burning cards

This commit is contained in:
tiger tiger tiger 2026-01-18 21:01:20 +01:00
parent 6e3be3b78b
commit ba4602d4b8
9 changed files with 127 additions and 148 deletions

View File

@ -1767,10 +1767,6 @@ mix_target = 2
bus = &"text" bus = &"text"
script = ExtResource("10_ypa88") script = ExtResource("10_ypa88")
[node name="CardBurner" parent="logic/UI" instance=ExtResource("11_5bsh1")]
visible = false
layout_mode = 1
[node name="ScenePlayer" type="AnimationPlayer" parent="logic" groups=["scene_actors"]] [node name="ScenePlayer" type="AnimationPlayer" parent="logic" groups=["scene_actors"]]
unique_name_in_owner = true unique_name_in_owner = true
libraries = { libraries = {

View File

@ -69,6 +69,8 @@ func _navigate_prev():
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready() -> void: func _ready() -> void:
print_debug("CardBoard.gd: %s._ready()" % self.name)
super._ready()
# HACK: Lets us debug more easily # HACK: Lets us debug more easily
if get_parent() == get_tree().root: if get_parent() == get_tree().root:
_debug_mode() _debug_mode()

View File

@ -100,31 +100,33 @@ func set_highlight(value: bool) -> void:
var burn_tween: Tween var burn_tween: Tween
signal has_burned signal has_burned
@export var burn_state: burned = burned.NOT:
var burn_state: burned = burned.NOT:
set(burning): set(burning):
if burning != burn_state: if burning != burn_state:
if burn_tween != null: if burn_tween: burn_tween.kill()
burn_tween.kill()
match burning: match burning:
burned.NOT: burned.NOT:
burn_tween = get_tree().create_tween() burn_tween = get_tree().create_tween()
burn_tween.tween_property(self, "burn_progress", 0, 0.5) burn_tween.tween_property(self, "burn_progress", 0, 0.5)
burn_state = burning
burned.SINGED: burned.SINGED:
burn_tween = get_tree().create_tween() burn_tween = get_tree().create_tween()
burn_tween.set_ease(Tween.EASE_OUT) burn_tween.set_ease(Tween.EASE_OUT)
burn_tween.set_trans(Tween.TRANS_SINE) burn_tween.set_trans(Tween.TRANS_SINE)
burn_tween.tween_property(self, "burn_progress", 0.5, 2) burn_tween.tween_property(self, "burn_progress", 0.5, 2)
burned.BURNING: burn_state = burning
burn_tween = get_tree().create_tween()
burn_tween.tween_property(self, "burn_progress", 2.0, 2)
burn_tween.tween_callback(_torch).set_delay(1.0)
burned.TORCHED: burned.TORCHED:
print_debug("Card %s has been burned." % HardCards.get_obscure_name(name)) print_debug("Card %s has been burned." % HardCards.get_obscure_name(name))
has_burned.emit() has_burned.emit()
burn_state = burning burn_state = burning
func _torch(): func torch():
if burn_tween: burn_tween.kill()
burn_state = burned.TORCHED burn_state = burned.TORCHED
burn_tween = create_tween()
burn_tween.tween_property(self, "burn_progress", 2.0, 2)
await burn_tween.finished
var crumble_material: ShaderMaterial = preload("res://logic-scenes/card_burner/card_crumble.material") var crumble_material: ShaderMaterial = preload("res://logic-scenes/card_burner/card_crumble.material")
var card_fire: Sprite2D = preload("res://logic-scenes/card_burner/card_fire.tscn").instantiate() var card_fire: Sprite2D = preload("res://logic-scenes/card_burner/card_fire.tscn").instantiate()

View File

@ -0,0 +1,9 @@
[gd_resource type="GradientTexture2D" load_steps=2 format=3 uid="uid://dwerd76p42ac"]
[sub_resource type="Gradient" id="Gradient_ckmi5"]
colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1)
[resource]
gradient = SubResource("Gradient_ckmi5")
width = 128
height = 128

View File

@ -1,28 +1,18 @@
class_name CardBurner class_name CardBurner
extends Playable extends Playable
var focused: bool = false:
set(focus):
if is_node_ready():
if not focus == focused:
if focus:
process_mode = Node.PROCESS_MODE_INHERIT
self.show()
Input.mouse_mode = Input.MOUSE_MODE_HIDDEN
if not PromptManager.icons == InputPrompt.Icons.KEYBOARD or true:
handle_direction_input(Vector2.UP)
else:
self.hide()
process_mode = Node.PROCESS_MODE_DISABLED
focused = focus
@onready var cursor: CandleCursor = %CandleCursor @onready var cursor: CandleCursor = %CandleCursor
@onready var ancors: Array[Control] = [%Ancor1, %Ancor2, %Ancor3, %Ancor4] @onready var ancors: Array[Control] = [%Ancor1, %Ancor2, %Ancor3, %Ancor4]
signal card_burned signal card_burned
var cards : Array[Card] = []
var _submitted := false
func _ready(): func _ready():
print_debug("CardBurner.gd: %s._ready()" % self.name)
super._ready()
%SkipButton.pressed.connect(card_burned.emit) %SkipButton.pressed.connect(card_burned.emit)
@ -39,19 +29,23 @@ func play() -> void:
print_debug("CardBurner: Found %d cards to choose from" % card_names.size()) print_debug("CardBurner: Found %d cards to choose from" % card_names.size())
# 2. Get card instances and shuffle them # 2. Get card instances and shuffle them
var random_cards: Array = HardCards.get_cards_by_name_array(card_names)["cards"] var stack : Array = HardCards.get_cards_by_name_array(card_names)["cards"]
random_cards.shuffle() stack.shuffle()
cards = []
# 3. Populate the 4 anchor slots with random cards # 3. Populate the 4 anchor slots with random cards
for ancor: Control in ancors: for ancor: Control in ancors:
var new_card: Card = random_cards.pop_front() var card: Card = stack.pop_front()
ancor.add_child(new_card) cards.append(card)
new_card.owner = self ancor.add_child(card)
new_card.has_burned.connect(card_burned.emit) print_debug("CardBurner: Added card '%s' to anchor" % card.name)
print_debug("CardBurner: Added card '%s' to anchor" % new_card.name)
print("CardBurner: ", len(cards))
# 4. Wait for player to burn a card (or skip) # 4. Wait for player to burn a card (or skip)
print_debug("CardBurner: Waiting for player to burn a card...") print_debug("CardBurner: Waiting for player to burn a card...")
handle_direction_input(Vector2.UP)
await card_burned await card_burned
# 5. Play vanish animation and wait for completion # 5. Play vanish animation and wait for completion
@ -61,17 +55,25 @@ func play() -> void:
print_debug("CardBurner: Sequence complete") print_debug("CardBurner: Sequence complete")
func handle_hover(card: Draggable):
if not card is Card: return
card.highlighted = card.mouse_over
card.burn_state = Card.burned.SINGED if card.mouse_over else Card.burned.NOT
func handle_mouse_button(event: InputEventMouseButton, card: Card): func handle_hover(card: Draggable) -> void:
if not card is Card: return
selection = cards.find(card)
func handle_mouse_button(event: InputEventMouseButton, card: Card) -> void:
if event.button_index == MOUSE_BUTTON_MASK_LEFT and event.is_pressed() and not event.is_echo(): if event.button_index == MOUSE_BUTTON_MASK_LEFT and event.is_pressed() and not event.is_echo():
card.burn_state = Card.burned.BURNING _submit(card)
func _submit(card : Card):
_submitted = true
%SkipButton.visible = false
await card.torch()
card_burned.emit()
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if focused: if _submitted: return
if event.is_action_pressed("ui_up"): if event.is_action_pressed("ui_up"):
handle_direction_input(Vector2.UP) handle_direction_input(Vector2.UP)
elif event.is_action_pressed("ui_down"): elif event.is_action_pressed("ui_down"):
@ -80,52 +82,55 @@ func _input(event: InputEvent) -> void:
handle_direction_input(Vector2.LEFT) handle_direction_input(Vector2.LEFT)
elif event.is_action_pressed("ui_right"): elif event.is_action_pressed("ui_right"):
handle_direction_input(Vector2.RIGHT) handle_direction_input(Vector2.RIGHT)
elif event is InputEventMouse:
cursor.gamepad_mode = false
ancors[card_index].get_child(0)._on_mouse_exited()
elif event.is_action_pressed("ui_accept"): elif event.is_action_pressed("ui_accept"):
ancors[card_index].get_child(0).burn_state = Card.burned.BURNING _submit(cards[selection])
var focus_cards: bool = false: var focus_cards: bool = false:
set(focus): set(focus):
if _submitted: return
focus_cards = focus focus_cards = focus
if focus_cards: if focus_cards:
cursor.visible = true cursor.visible = true
%SkipButton.release_focus() %SkipButton.release_focus()
card_index = card_index
else: else:
cursor.visible = false cursor.visible = false
%SkipButton.grab_focus() %SkipButton.grab_focus()
ancors[card_index].get_child(0)._on_mouse_exited()
var card_index: int = 0:
set(index): var selection: int:
ancors[card_index].get_child(0)._on_mouse_exited() set(value):
card_index = index % 4 if _submitted: return
handle_hover(ancors[card_index].get_child(0)) selection = value % len(cards)
func handle_direction_input(direction: Vector2): for i in range(len(cards)):
var card := cards[i]
card.highlighted = (selection == i)
card.burn_state = Card.burned.SINGED if card.highlighted else Card.burned.NOT
if card.highlighted:
cursor.gamepad_target = card.global_position + Vector2(-120, 150)
func handle_direction_input(direction: Vector2) -> void:
if _submitted: return
if not cursor.gamepad_mode: if not cursor.gamepad_mode:
for ancor in ancors:
ancor.get_child(0)._on_mouse_exited()
cursor.gamepad_mode = true cursor.gamepad_mode = true
focus_cards = focus_cards focus_cards = focus_cards
match direction: match direction:
Vector2.UP: Vector2.UP:
focus_cards = true focus_cards = true
cursor.visible = true cursor.visible = true
%SkipButton.release_focus() %SkipButton.release_focus()
card_index = card_index selection = selection
Vector2.DOWN: Vector2.DOWN:
focus_cards = false focus_cards = false
cursor.visible = false cursor.visible = false
%SkipButton.grab_focus() %SkipButton.grab_focus()
ancors[card_index].get_child(0)._on_mouse_exited()
Vector2.LEFT: Vector2.LEFT:
focus_cards = true focus_cards = true
card_index -= 1 selection -= 1
Vector2.RIGHT: Vector2.RIGHT:
focus_cards = true focus_cards = true
card_index += 1 selection += 1
cursor.gamepad_target = ancors[card_index].get_child(0).global_position + Vector2(-120, 150)
if not focus_cards: cursor.gamepad_target += Vector2(0, 50) if not focus_cards: cursor.gamepad_target += Vector2(0, 50)

View File

@ -1,10 +1,10 @@
[gd_scene load_steps=15 format=3 uid="uid://g2a27jwdapai"] [gd_scene load_steps=13 format=3 uid="uid://g2a27jwdapai"]
[ext_resource type="Script" uid="uid://bbia2hcdwctyn" path="res://logic-scenes/card_burner/card_burner.gd" id="1_copuj"] [ext_resource type="Script" uid="uid://bbia2hcdwctyn" path="res://logic-scenes/card_burner/card_burner.gd" id="1_copuj"]
[ext_resource type="Texture2D" uid="uid://615hvpuiacvm" path="res://addons/input_prompts/icons/xbox/X.png" id="3_ckmi5"] [ext_resource type="Texture2D" uid="uid://615hvpuiacvm" path="res://addons/input_prompts/icons/xbox/X.png" id="3_ckmi5"]
[ext_resource type="PackedScene" uid="uid://uc6urpgv7n1y" path="res://logic-scenes/card_burner/cursor_candle.tscn" id="3_l4ogr"] [ext_resource type="PackedScene" uid="uid://uc6urpgv7n1y" path="res://logic-scenes/card_burner/cursor_candle.tscn" id="3_l4ogr"]
[ext_resource type="Texture2D" uid="uid://dwerd76p42ac" path="res://logic-scenes/card_burner/burner_gradient.tres" id="4_ckmi5"]
[ext_resource type="Script" uid="uid://bbs1u7ojno7xo" path="res://addons/input_prompts/action_prompt/action_prompt.gd" id="4_x6cxt"] [ext_resource type="Script" uid="uid://bbs1u7ojno7xo" path="res://addons/input_prompts/action_prompt/action_prompt.gd" id="4_x6cxt"]
[ext_resource type="PackedScene" uid="uid://dy5rd437h5hsw" path="res://logic-scenes/board/card.tscn" id="5_ckmi5"]
[sub_resource type="InputEventKey" id="InputEventKey_ckmi5"] [sub_resource type="InputEventKey" id="InputEventKey_ckmi5"]
device = -1 device = -1
@ -22,20 +22,12 @@ action = &"skip"
[sub_resource type="Shortcut" id="Shortcut_57mhv"] [sub_resource type="Shortcut" id="Shortcut_57mhv"]
events = [SubResource("InputEventAction_23lqb")] events = [SubResource("InputEventAction_23lqb")]
[sub_resource type="Gradient" id="Gradient_ckmi5"]
colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_x6cxt"]
gradient = SubResource("Gradient_ckmi5")
width = 100
height = 100
[sub_resource type="Animation" id="Animation_57mhv"] [sub_resource type="Animation" id="Animation_57mhv"]
length = 0.001 length = 0.001
tracks/0/type = "value" tracks/0/type = "value"
tracks/0/imported = false tracks/0/imported = false
tracks/0/enabled = true tracks/0/enabled = true
tracks/0/path = NodePath("../Sprite2D:self_modulate") tracks/0/path = NodePath("../../Sprite2D:self_modulate")
tracks/0/interp = 1 tracks/0/interp = 1
tracks/0/loop_wrap = true tracks/0/loop_wrap = true
tracks/0/keys = { tracks/0/keys = {
@ -62,7 +54,7 @@ resource_name = "vanish"
tracks/0/type = "value" tracks/0/type = "value"
tracks/0/imported = false tracks/0/imported = false
tracks/0/enabled = true tracks/0/enabled = true
tracks/0/path = NodePath("../Sprite2D:self_modulate") tracks/0/path = NodePath("../../Sprite2D:self_modulate")
tracks/0/interp = 2 tracks/0/interp = 2
tracks/0/loop_wrap = true tracks/0/loop_wrap = true
tracks/0/keys = { tracks/0/keys = {
@ -98,6 +90,12 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("1_copuj") script = ExtResource("1_copuj")
[node name="ColorRect" type="ColorRect" parent="."]
visible = false
custom_minimum_size = Vector2(8192, 8192)
layout_mode = 2
color = Color(0, 0, 0, 0.3137255)
[node name="Control" type="Control" parent="."] [node name="Control" type="Control" parent="."]
layout_mode = 2 layout_mode = 2
@ -185,56 +183,18 @@ layout_mode = 2
shortcut = SubResource("Shortcut_57mhv") shortcut = SubResource("Shortcut_57mhv")
text = "Keep all thoughts" text = "Keep all thoughts"
[node name="Sprite2D" type="Sprite2D" parent="Control"]
self_modulate = Color(1, 1, 1, 0)
scale = Vector2(100, 100)
texture = SubResource("GradientTexture2D_x6cxt")
[node name="CandleCursor" parent="." instance=ExtResource("3_l4ogr")] [node name="CandleCursor" parent="." instance=ExtResource("3_l4ogr")]
unique_name_in_owner = true unique_name_in_owner = true
position = Vector2(989, 1068)
[node name="Sprite2D" type="Sprite2D" parent="."]
self_modulate = Color(1, 1, 1, 0)
position = Vector2(954.99994, 545)
scale = Vector2(100, 100)
texture = ExtResource("4_ckmi5")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."] [node name="AnimationPlayer" type="AnimationPlayer" parent="."]
root_node = NodePath("../Control/HSplitContainer") root_node = NodePath("../Control/HSplitContainer")
libraries = { libraries = {
&"": SubResource("AnimationLibrary_kaqqi") &"": SubResource("AnimationLibrary_kaqqi")
} }
[node name="Card1" parent="." instance=ExtResource("5_ckmi5")]
wiggle_strength = null
wiggle_speed = null
scale_bump = null
bounce_speed = null
is_dragable = null
diameter = null
burn_progress = null
burn_state = null
[node name="Card2" parent="." instance=ExtResource("5_ckmi5")]
wiggle_strength = null
wiggle_speed = null
scale_bump = null
bounce_speed = null
is_dragable = null
diameter = null
burn_progress = null
burn_state = null
[node name="Card3" parent="." instance=ExtResource("5_ckmi5")]
wiggle_strength = null
wiggle_speed = null
scale_bump = null
bounce_speed = null
is_dragable = null
diameter = null
burn_progress = null
burn_state = null
[node name="Card4" parent="." instance=ExtResource("5_ckmi5")]
wiggle_strength = null
wiggle_speed = null
scale_bump = null
bounce_speed = null
is_dragable = null
diameter = null
burn_progress = null
burn_state = null

View File

@ -1,7 +1,7 @@
class_name Interactable extends Node3D class_name Interactable extends Node3D
@export var interaction: PackedScene = null @export var interaction: PackedScene = null
var interaction_ui : Playable = null var playable : Playable = null
@onready var view: Node3D = $View @onready var view: Node3D = $View
@onready var frame: Sprite3D = $Frame @onready var frame: Sprite3D = $Frame
@ -30,12 +30,12 @@ func _ready() -> void:
view.scale = Vector3.ZERO view.scale = Vector3.ZERO
frame.modulate.a = 0 frame.modulate.a = 0
if interaction: if interaction:
interaction_ui = interaction.instantiate() as Control playable = interaction.instantiate() as Control
canvas_layer.add_child(interaction_ui) canvas_layer.add_child(playable)
_update_caption() _update_caption()
# Check if this scene was already completed (for re-entering rooms) # Check if this scene was already completed (for re-entering rooms)
if interaction_ui is StoryPlayable: if playable is StoryPlayable:
var story := interaction_ui as StoryPlayable var story := playable as StoryPlayable
collected = Scenes.is_sequence_repeating(story.scene_id) collected = Scenes.is_sequence_repeating(story.scene_id)
else: else:
_update_prompt() _update_prompt()
@ -106,19 +106,19 @@ func play_story() -> void:
canvas_layer.show() canvas_layer.show()
# Allow room to prepare for scene (e.g., play animations) # Allow room to prepare for scene (e.g., play animations)
await State.room.prepare_scene_start(interaction_ui.scene_id, repeat) await State.room.prepare_scene_start(playable.scene_id, repeat)
Scenes.begin_sequence(interaction_ui.scene_id) Scenes.begin_sequence(playable.scene_id)
# Play the story # Play the story
await interaction_ui.play() await playable.play()
# Pick the cards if not already picked # Pick the cards if not already picked
if not repeat: if not repeat:
var picker := State.room.get_node("%Picker") as CardPicker var picker := State.room.get_node("%Picker") as CardPicker
await picker.pick_cards(interaction_ui.scene_id) await picker.pick_cards(playable.scene_id)
Scenes.end_sequence(interaction_ui.scene_id) # todo: maybe later? Scenes.end_sequence(playable.scene_id) # todo: maybe later?
# Hide the CanvasLayer when done # Hide the CanvasLayer when done
canvas_layer.hide() canvas_layer.hide()
@ -131,7 +131,7 @@ func play_board() -> void:
canvas_layer.show() canvas_layer.show()
# Play the board (handles mouse visibility and waits for close) # Play the board (handles mouse visibility and waits for close)
await interaction_ui.play() await playable.play()
# Hide the CanvasLayer when done # Hide the CanvasLayer when done
canvas_layer.hide() canvas_layer.hide()
@ -144,7 +144,7 @@ func play_burner() -> void:
canvas_layer.show() canvas_layer.show()
# Play the board (handles mouse visibility and waits for close) # Play the board (handles mouse visibility and waits for close)
await interaction_ui.play() await playable.play()
# Hide the CanvasLayer when done # Hide the CanvasLayer when done
canvas_layer.hide() canvas_layer.hide()
@ -159,32 +159,34 @@ func interact() -> void:
# collapse other interactables BEFORE showing canvas # collapse other interactables BEFORE showing canvas
get_tree().call_group("interactables", "collapse") get_tree().call_group("interactables", "collapse")
if interaction_ui is StoryPlayable:
if playable is StoryPlayable:
await play_story() await play_story()
if interaction_ui is CardBoard: if playable is CardBoard:
await play_board() await play_board()
if interaction_ui is CardBurner: if playable is CardBurner:
await play_burner() await play_burner()
## Updates caption label based on the instantiated interaction_ui ## Updates caption label based on the instantiated interaction_ui
func _update_caption() -> void: func _update_caption() -> void:
if interaction_ui is StoryPlayable: if playable is StoryPlayable:
var story := interaction_ui as StoryPlayable var story := playable as StoryPlayable
caption.text = I18n.get_story_caption(story.scene_id) caption.text = I18n.get_story_caption(story.scene_id)
if interaction_ui is CardBoard: if playable is CardBoard:
caption.text = TranslationServer.translate("Mind Board") caption.text = TranslationServer.translate("Mind Board")
if interaction_ui is CardBurner: if playable is CardBurner:
caption.text = TranslationServer.translate("leave") caption.text = TranslationServer.translate("leave")
## Updates prompt label based on the interaction type and collected state ## Updates prompt label based on the interaction type and collected state
func _update_prompt() -> void: func _update_prompt() -> void:
if interaction_ui is StoryPlayable: if playable is StoryPlayable:
if collected: if collected:
prompt.text = TranslationServer.translate("read again") prompt.text = TranslationServer.translate("read again")
else: else:
prompt.text = TranslationServer.translate("MementoLabel_collect") prompt.text = TranslationServer.translate("MementoLabel_collect")
elif interaction_ui is CardBoard: elif playable is CardBoard:
prompt.text = TranslationServer.translate("find connections") prompt.text = TranslationServer.translate("find connections")

View File

@ -70,7 +70,7 @@ var substring_sizes: Array[int]
func _ready() -> void: func _ready() -> void:
print_debug("StoryPlayable.gd: %s._ready()" % self.name) print_debug("StoryPlayable.gd: %s._ready()" % self.name)
hide() super._ready()
animation_player.play("RESET") animation_player.play("RESET")
State.settings_changed.connect(func(): story_array = story_array) State.settings_changed.connect(func(): story_array = story_array)
skip_control = %SkipControl skip_control = %SkipControl

View File

@ -1,6 +1,9 @@
extends Control extends Control
class_name Playable class_name Playable
func _ready() -> void:
pass
## Awaitable that encapsulates the core interaction with this Playable ## Awaitable that encapsulates the core interaction with this Playable
func play() -> void: func play() -> void:
push_warning("Playeable[base].play() not overridden") push_warning("Playeable[base].play() not overridden")