class_name Interactable extends Node3D @export var interaction: PackedScene = null var interaction_ui : Control = null @onready var view: Node3D = $View @onready var frame: Sprite3D = $Frame @onready var canvas_layer: CanvasLayer = $CanvasLayer @onready var caption : Label3D = %Caption @onready var prompt : Label3D = %Prompt var active : bool = true var shown : bool = false var hover : bool = false var collected : bool = false: set(value): collected = value if is_inside_tree(): _update_prompt() var tween: Tween = null func _ready() -> void: view.scale = Vector3.ZERO frame.modulate.a = 0 if interaction: interaction_ui = interaction.instantiate() as Control canvas_layer.add_child(interaction_ui) _update_caption() # Check if this scene was already completed (for re-entering rooms) if interaction_ui is StoryPlayable: var story := interaction_ui as StoryPlayable collected = Scenes.is_sequence_repeating(story.scene_id) else: _update_prompt() Scenes.player_enable.connect(_player_active) # TODO: do I have to clean this up? func _player_active(value: bool) -> void: active = value func collapse() -> void: hover = false if shown: shown = false if tween: tween.kill() tween = create_tween().set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_BACK) tween.parallel().tween_property(view, "scale", Vector3.ZERO, 0.3) tween.parallel().tween_property(frame, "modulate:a", 0, 0.6) func _process(_delta: float) -> void: _process_hover() _process_billboard() func _process_billboard() -> void: if shown: var player_view := State.player_view look_at(player_view.global_position, Vector3.UP, true) func _process_hover() -> void: if active and hover and not shown: shown = true view.scale = Vector3.ZERO frame.modulate = Color.TRANSPARENT view.rotation.z = -PI*0.5 # Godot angle wrapping is ... something if tween: tween.kill() tween = create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) tween.parallel().tween_property(view, "scale", Vector3.ONE, 1.0).set_delay(0.5) tween.parallel().tween_property(view, "rotation:z", 0, 0.8).set_delay(0.5) tween.parallel().tween_property(frame, "modulate:a", 1.0, 2.0) elif not hover and shown or shown and not active: shown = false if tween: tween.kill() tween = create_tween().set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_BACK) tween.parallel().tween_property(view, "scale", Vector3.ZERO, 0.3) tween.parallel().tween_property(frame, "modulate:a", 0, 0.6) func _input(event: InputEvent) -> void: if not active or not hover or not shown: return var clicked : bool = (event.is_action_pressed("ui_accept")) or (event is InputEventMouseButton and event.pressed) if hover and shown and clicked: collect_memento() func play_story() -> void: canvas_layer.show() Scenes.begin_sequence(interaction_ui.scene_id) # Play the story await interaction_ui.play() # Pick the cards var picker := State.room.get_node("%Picker") as CardPicker await picker.pick_cards(interaction_ui.scene_id, false) # Hide the CanvasLayer when done canvas_layer.hide() Scenes.end_sequence(interaction_ui.scene_id) # todo: maybe later? Scenes.player_enable.emit(true) # TODO: this may not be our job? func play_board() -> void: canvas_layer.show() # Play the board (handles mouse visibility and waits for close) await interaction_ui.play() # Hide the CanvasLayer when done canvas_layer.hide() Scenes.player_enable.emit(true) func collect_memento() -> void: shown = false collected = true # Hide mouse and collapse other interactables BEFORE showing canvas get_tree().call_group("interactables", "collapse") # Show the CanvasLayer so the story is visible full-screen canvas_layer.show() if interaction_ui is StoryPlayable: play_story() if interaction_ui is CardBoard: play_board() ## Updates caption label based on the instantiated interaction_ui func _update_caption() -> void: if interaction_ui is StoryPlayable: var story := interaction_ui as StoryPlayable match story.scene_id: Scenes.id.YOUTH_DRAVEN: caption.text = TranslationServer.translate("Starlight") Scenes.id.YOUTH_CHILDHOOD: caption.text = TranslationServer.translate("crafted Mask") Scenes.id.YOUTH_VOICE_TRAINING: caption.text = TranslationServer.translate("Comic Stash") Scenes.id.YOUTH_JUI_JUTSU: caption.text = TranslationServer.translate("Sports Clothes") Scenes.id.TRANSITION: caption.text = TranslationServer.translate("Move on") Scenes.id.ADULT_DND: caption.text = TranslationServer.translate("colorful Dice") Scenes.id.ADULT_VOLUNTARY: caption.text = TranslationServer.translate("Gemstone Art") Scenes.id.ADULT_CHRISTMAS: caption.text = TranslationServer.translate("Chat Messages") Scenes.id.ADULT_EATING: caption.text = TranslationServer.translate("Dishes") Scenes.id.ADULT_UNI: caption.text = TranslationServer.translate("Science Poster") Scenes.id.ADULT_THERAPY: caption.text = TranslationServer.translate("Doctors Note") Scenes.id.ADULT_BURNOUT: caption.text = TranslationServer.translate("Paperwork") _: caption.text = "" elif interaction_ui is CardBoard: caption.text = TranslationServer.translate("Mind Board") ## Updates prompt label based on the interaction type and collected state func _update_prompt() -> void: if interaction_ui is StoryPlayable: if collected: prompt.text = TranslationServer.translate("read again") else: prompt.text = TranslationServer.translate("MementoLabel_collect") elif interaction_ui is CardBoard: prompt.text = TranslationServer.translate("find connections")