Files
tekton/launcher/scripts/launcher_main.gd
T
2025-12-10 02:35:17 +08:00

333 lines
11 KiB
GDScript

extends Control
## Main launcher scene script - orchestrates all launcher functionality
# Child nodes (will be set up in _ready)
@onready var version_checker := $VersionChecker as VersionChecker
@onready var download_manager := $DownloadManager as DownloadManager
@onready var update_applier := $UpdateApplier as UpdateApplier
@onready var news_fetcher := $NewsFetcher as NewsFetcher
# UI Elements
@onready var play_button := $MainPanel/ContentContainer/BottomBar/PlayButton as Button
@onready var progress_bar := $MainPanel/ContentContainer/BottomBar/ProgressContainer/ProgressBar as ProgressBar
@onready var progress_label := $MainPanel/ContentContainer/BottomBar/ProgressContainer/ProgressLabel as Label
@onready var progress_container := $MainPanel/ContentContainer/BottomBar/ProgressContainer as Control
@onready var status_label := $MainPanel/ContentContainer/BottomBar/StatusLabel as Label
@onready var version_label := $MainPanel/TitleBar/VersionLabel as Label
@onready var news_container := $MainPanel/ContentContainer/TabContainer/News/ScrollContainer/NewsVBox as VBoxContainer
@onready var changelog_container := $MainPanel/ContentContainer/TabContainer/Changelog/ScrollContainer/ChangelogVBox as VBoxContainer
enum LauncherState {
CHECKING,
UP_TO_DATE,
UPDATE_AVAILABLE,
DOWNLOADING,
INSTALLING,
READY,
ERROR
}
var current_state: LauncherState = LauncherState.CHECKING
var pending_changelog: Array = []
func _ready() -> void:
_setup_signals()
_apply_theme()
_set_state(LauncherState.CHECKING)
# Start checking for updates
await get_tree().create_timer(0.5).timeout # Small delay for UI to initialize
version_checker.check_for_updates()
news_fetcher.fetch_news()
func _setup_signals() -> void:
# Version checker signals
version_checker.version_check_completed.connect(_on_version_check_completed)
version_checker.version_check_failed.connect(_on_version_check_failed)
# Download manager signals
download_manager.download_started.connect(_on_download_started)
download_manager.download_progress.connect(_on_download_progress)
download_manager.download_completed.connect(_on_download_completed)
download_manager.download_failed.connect(_on_download_failed)
# Update applier signals
update_applier.update_started.connect(_on_update_started)
update_applier.update_progress.connect(_on_update_progress)
update_applier.update_completed.connect(_on_update_completed)
update_applier.update_failed.connect(_on_update_failed)
# News fetcher signals
news_fetcher.news_fetched.connect(_on_news_fetched)
# Play button
play_button.pressed.connect(_on_play_pressed)
func _apply_theme() -> void:
# Apply theme colors from config
var bg_style := StyleBoxFlat.new()
bg_style.bg_color = Config.COLOR_BACKGROUND
add_theme_stylebox_override("panel", bg_style)
func _set_state(new_state: LauncherState) -> void:
current_state = new_state
match new_state:
LauncherState.CHECKING:
status_label.text = "Checking for updates..."
play_button.disabled = true
play_button.text = "CHECKING..."
progress_container.visible = false
LauncherState.UP_TO_DATE:
status_label.text = "Game is up to date!"
play_button.disabled = false
play_button.text = "► PLAY"
progress_container.visible = false
version_label.text = "v" + version_checker.current_version
LauncherState.UPDATE_AVAILABLE:
status_label.text = "Update available: v" + version_checker.latest_version
play_button.disabled = false
play_button.text = "⬇ UPDATE"
progress_container.visible = false
LauncherState.DOWNLOADING:
status_label.text = "Downloading update..."
play_button.disabled = true
play_button.text = "DOWNLOADING..."
progress_container.visible = true
progress_bar.value = 0
LauncherState.INSTALLING:
status_label.text = "Installing update..."
play_button.disabled = true
play_button.text = "INSTALLING..."
progress_container.visible = true
LauncherState.READY:
status_label.text = "Ready to play!"
play_button.disabled = false
play_button.text = "► PLAY"
progress_container.visible = false
version_label.text = "v" + version_checker.current_version
LauncherState.ERROR:
play_button.disabled = true
play_button.text = "ERROR"
progress_container.visible = false
# --- Version Check Callbacks ---
func _on_version_check_completed(has_update: bool, latest_version: String, changelog: Array) -> void:
pending_changelog = changelog
_populate_changelog(changelog)
if has_update:
_set_state(LauncherState.UPDATE_AVAILABLE)
else:
# Check if game is installed
if _is_game_installed():
_set_state(LauncherState.UP_TO_DATE)
else:
# Fresh install needed
status_label.text = "Game not installed - click UPDATE to download"
_set_state(LauncherState.UPDATE_AVAILABLE)
func _on_version_check_failed(error: String) -> void:
status_label.text = "Failed to check for updates: " + error
# Still allow playing if game is installed
if _is_game_installed():
_set_state(LauncherState.READY)
status_label.text = "Offline mode - " + error
else:
_set_state(LauncherState.ERROR)
# --- Download Callbacks ---
func _on_download_started(_file_name: String, _total_size: int) -> void:
_set_state(LauncherState.DOWNLOADING)
func _on_download_progress(_downloaded: int, _total: int, percentage: float) -> void:
progress_bar.value = percentage
progress_label.text = "%.1f%%" % percentage
func _on_download_completed(file_path: String) -> void:
_set_state(LauncherState.INSTALLING)
update_applier.apply_update(file_path, version_checker.latest_version)
func _on_download_failed(error: String) -> void:
status_label.text = "Download failed: " + error
_set_state(LauncherState.ERROR)
# --- Update Callbacks ---
func _on_update_started() -> void:
progress_bar.value = 0
func _on_update_progress(step: String, percentage: float) -> void:
progress_bar.value = percentage
progress_label.text = step
func _on_update_completed() -> void:
version_checker.save_version(version_checker.latest_version)
_set_state(LauncherState.READY)
status_label.text = "Update complete! Ready to play."
func _on_update_failed(error: String) -> void:
status_label.text = "Update failed: " + error
_set_state(LauncherState.ERROR)
# --- News Callbacks ---
func _on_news_fetched(news_items: Array) -> void:
_populate_news(news_items)
# --- UI Helpers ---
func _populate_changelog(changelog: Array) -> void:
# Clear existing
for child in changelog_container.get_children():
child.queue_free()
if changelog.is_empty():
var label := Label.new()
label.text = "No changelog available"
label.add_theme_color_override("font_color", Config.COLOR_TEXT_DIM)
changelog_container.add_child(label)
return
for entry in changelog:
var version_label_item := Label.new()
version_label_item.text = "Version " + entry.get("version", "?") + " - " + entry.get("date", "")
version_label_item.add_theme_color_override("font_color", Config.COLOR_PRIMARY)
version_label_item.add_theme_font_size_override("font_size", 16)
changelog_container.add_child(version_label_item)
var changes: Array = entry.get("changes", [])
for change in changes:
var change_label := Label.new()
change_label.text = " • " + str(change)
change_label.add_theme_color_override("font_color", Config.COLOR_TEXT)
changelog_container.add_child(change_label)
# Spacer
var spacer := Control.new()
spacer.custom_minimum_size = Vector2(0, 10)
changelog_container.add_child(spacer)
func _populate_news(news_items: Array) -> void:
# Clear existing
for child in news_container.get_children():
child.queue_free()
if news_items.is_empty():
var label := Label.new()
label.text = "No news available"
label.add_theme_color_override("font_color", Config.COLOR_TEXT_DIM)
news_container.add_child(label)
return
for item in news_items:
# News card panel
var card := PanelContainer.new()
var card_style := StyleBoxFlat.new()
card_style.bg_color = Config.COLOR_SURFACE
card_style.set_corner_radius_all(8)
card_style.set_content_margin_all(12)
card.add_theme_stylebox_override("panel", card_style)
var vbox := VBoxContainer.new()
card.add_child(vbox)
# Title
var title := Label.new()
title.text = item.get("title", "Untitled")
title.add_theme_color_override("font_color", Config.COLOR_PRIMARY)
title.add_theme_font_size_override("font_size", 16)
vbox.add_child(title)
# Date
var date := Label.new()
date.text = item.get("date", "")
date.add_theme_color_override("font_color", Config.COLOR_TEXT_DIM)
date.add_theme_font_size_override("font_size", 12)
vbox.add_child(date)
# Content
var content := Label.new()
content.text = item.get("content", "")
content.add_theme_color_override("font_color", Config.COLOR_TEXT)
content.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
vbox.add_child(content)
news_container.add_child(card)
# Spacer
var spacer := Control.new()
spacer.custom_minimum_size = Vector2(0, 8)
news_container.add_child(spacer)
func _is_game_installed() -> bool:
var pck_path := Config.GAME_DIRECTORY + Config.get_game_pck()
return FileAccess.file_exists(pck_path)
func _on_play_pressed() -> void:
match current_state:
LauncherState.UPDATE_AVAILABLE:
# Start download
var download_info := version_checker.get_download_info()
if download_info.get("url", "") != "":
download_manager.download_file(
download_info.url,
Config.get_game_pck(),
download_info.get("size", 0),
download_info.get("checksum_md5", ""),
download_info.get("checksum_sha256", "")
)
else:
status_label.text = "Error: No download URL available"
_set_state(LauncherState.ERROR)
LauncherState.UP_TO_DATE, LauncherState.READY:
_launch_game()
func _launch_game() -> void:
var game_executable := Config.get_game_executable()
var game_path := ProjectSettings.globalize_path(Config.GAME_DIRECTORY + game_executable)
if not FileAccess.file_exists(Config.GAME_DIRECTORY + game_executable):
# Try to find the executable in the same directory as launcher
var launcher_dir := OS.get_executable_path().get_base_dir()
game_path = launcher_dir + "/" + game_executable
print("[Launcher] Platform: ", Config.get_platform_name())
print("[Launcher] Launching game: ", game_path)
var args: PackedStringArray = []
var pid: int = -1
# Platform-specific launch handling
match Config.get_current_platform():
Config.Platform.MACOS:
# macOS: Use 'open' command for .app bundles
if game_executable.ends_with(".app"):
pid = OS.create_process("open", PackedStringArray(["-a", game_path]))
else:
pid = OS.create_process(game_path, args)
Config.Platform.LINUX:
# Linux: May need to set executable permission
OS.execute("chmod", PackedStringArray(["+x", game_path]))
pid = OS.create_process(game_path, args)
_:
# Windows and others
pid = OS.create_process(game_path, args)
if pid > 0:
# Successfully launched, close launcher
get_tree().quit()
else:
status_label.text = "Failed to launch game"
_set_state(LauncherState.ERROR)