extends Control signal back_pressed const YAML_SOURCES: Array[String] = [ "res://assets/sources.yaml", # Future sources: # "res://assets/audio/audio-sources.yaml", # "res://assets/sprites/sprite-sources.yaml", ] @onready var scroll_container: ScrollContainer = $MarginContainer/VBoxContainer/ScrollContainer @onready var credits_text: RichTextLabel = $MarginContainer/VBoxContainer/ScrollContainer/CreditsText @onready var back_button: Button = $MarginContainer/VBoxContainer/BackButton func _ready() -> void: DebugManager.log_info("Credits scene ready", "Credits") _load_and_display_credits() back_button.grab_focus() DebugManager.log_info("Back button focused for gamepad support", "Credits") func _load_and_display_credits() -> void: """Load credits from multiple YAML files and display formatted output""" var all_credits_data: Dictionary = {} # Load all YAML source files for yaml_path in YAML_SOURCES: var yaml_data: Dictionary = _load_yaml_file(yaml_path) if not yaml_data.is_empty(): _merge_credits_data(all_credits_data, yaml_data) # Generate and display formatted credits _display_formatted_credits(all_credits_data) func _load_yaml_file(yaml_path: String) -> Dictionary: """Load and parse a YAML file into a dictionary structure""" var file := FileAccess.open(yaml_path, FileAccess.READ) if not file: DebugManager.log_warn( "Could not open YAML file: %s" % yaml_path, "Credits" ) return {} var content: String = file.get_as_text() file.close() DebugManager.log_info("Loaded YAML file: %s" % yaml_path, "Credits") return _parse_yaml_content(content) func _parse_yaml_content(yaml_content: String) -> Dictionary: """Parse YAML content into structured dictionary""" var result: Dictionary = {} var lines: Array = yaml_content.split("\n") var current_section: String = "" var current_subsection: String = "" var current_asset: String = "" var current_asset_data: Dictionary = {} for line in lines: var trimmed: String = line.strip_edges() # Skip comments and empty lines if trimmed.is_empty() or trimmed.begins_with("#"): continue # Top-level section (audio, sprites, textures, etc.) if ( not line.begins_with(" ") and not line.begins_with("\t") and trimmed.ends_with(":") ): if current_asset and not current_asset_data.is_empty(): _store_asset_data( result, current_section, current_subsection, current_asset, current_asset_data ) current_section = trimmed.trim_suffix(":") current_subsection = "" current_asset = "" current_asset_data = {} if not result.has(current_section): result[current_section] = {} # Subsection (music, sfx, characters, etc.) elif ( line.begins_with(" ") and not line.begins_with(" ") and trimmed.ends_with(":") ): if current_asset and not current_asset_data.is_empty(): _store_asset_data( result, current_section, current_subsection, current_asset, current_asset_data ) current_subsection = trimmed.trim_suffix(":") current_asset = "" current_asset_data = {} if ( current_section and not result[current_section].has(current_subsection) ): result[current_section][current_subsection] = {} # Asset name elif trimmed.begins_with('"') and trimmed.contains('":'): if current_asset and not current_asset_data.is_empty(): _store_asset_data( result, current_section, current_subsection, current_asset, current_asset_data ) var parts: Array = trimmed.split('"') current_asset = parts[1] if parts.size() > 1 else "" current_asset_data = {} # Asset properties elif ":" in trimmed: var parts: Array = trimmed.split(":", false, 1) if parts.size() == 2: var key: String = parts[0].strip_edges() var value: String = ( parts[1].strip_edges().trim_prefix('"').trim_suffix('"') ) if value and value != '""': current_asset_data[key] = value # Store last asset if current_asset and not current_asset_data.is_empty(): _store_asset_data( result, current_section, current_subsection, current_asset, current_asset_data ) return result func _store_asset_data( result: Dictionary, section: String, subsection: String, asset: String, data: Dictionary ) -> void: """Store parsed asset data into result dictionary""" if not section or not asset: return if subsection: if not result[section].has(subsection): result[section][subsection] = {} result[section][subsection][asset] = data else: result[section][asset] = data func _merge_credits_data(target: Dictionary, source: Dictionary) -> void: """Merge source credits data into target dictionary""" for section in source: if not target.has(section): target[section] = {} for subsection in source[section]: if source[section][subsection] is Dictionary: if not target[section].has(subsection): target[section][subsection] = {} for asset in source[section][subsection]: target[section][subsection][asset] = source[section][subsection][asset] func _display_formatted_credits(credits_data: Dictionary) -> void: """Generate BBCode formatted credits from parsed data""" if not credits_text: DebugManager.log_error( "Credits text node is null, cannot display credits", "Credits" ) return var credits_bbcode: String = "[center][b][font_size=32]CREDITS[/font_size][/b][/center]\n\n" # Audio section if credits_data.has("audio"): credits_bbcode += "[b][font_size=24]AUDIO[/font_size][/b]\n\n" credits_bbcode += _format_section(credits_data["audio"]) # Sprites section if credits_data.has("sprites"): credits_bbcode += "[b][font_size=24]GRAPHICS[/font_size][/b]\n\n" credits_bbcode += _format_section(credits_data["sprites"]) # Textures section if credits_data.has("textures"): if not credits_data.has("sprites"): credits_bbcode += "[b][font_size=24]GRAPHICS[/font_size][/b]\n\n" credits_bbcode += _format_section(credits_data["textures"]) # Game development credits credits_bbcode += "[b][font_size=24]GAME DEVELOPMENT[/font_size][/b]\n\n" credits_bbcode += "[i]Developed with Godot Engine 4.4[/i]\n" credits_bbcode += "[url=https://godotengine.org]https://godotengine.org[/url]\n\n" credits_text.bbcode_enabled = true credits_text.text = credits_bbcode DebugManager.log_info("Credits displayed successfully", "Credits") func _format_section(section_data: Dictionary) -> String: """Format a credits section with subsections and assets""" var result: String = "" for subsection in section_data: if section_data[subsection] is Dictionary: # Add subsection header var subsection_title: String = subsection.capitalize() result += "[i]" + subsection_title + "[/i]\n" # Add assets in this subsection for asset in section_data[subsection]: var asset_data: Dictionary = section_data[subsection][asset] result += _format_asset(asset_data) result += "\n" return result func _format_asset(asset_data: Dictionary) -> String: """Format a single asset's credit information""" var result: String = "" if asset_data.has("attribution") and asset_data["attribution"]: result += "[b]" + asset_data["attribution"] + "[/b]\n" if asset_data.has("license") and asset_data["license"]: result += "License: " + asset_data["license"] + "\n" if asset_data.has("source") and asset_data["source"]: result += "[url=" + asset_data["source"] + "]Source[/url]\n" if result: result += "\n" return result func _on_back_button_pressed() -> void: AudioManager.play_ui_click() DebugManager.log_info("Back button pressed", "Credits") GameManager.exit_to_main_menu() func _input(event: InputEvent) -> void: if ( event.is_action_pressed("ui_back") or event.is_action_pressed("action_east") ): _on_back_button_pressed() elif event.is_action_pressed("move_up") or event.is_action_pressed("ui_up"): _scroll_credits(-50.0) elif ( event.is_action_pressed("move_down") or event.is_action_pressed("ui_down") ): _scroll_credits(50.0) func _scroll_credits(amount: float) -> void: """Scroll the credits by the specified amount""" var current_scroll: float = scroll_container.scroll_vertical scroll_container.scroll_vertical = int(current_scroll + amount) DebugManager.log_debug( "Scrolled credits to: %d" % scroll_container.scroll_vertical, "Credits" )