feat: patch system
This commit is contained in:
@@ -14,7 +14,7 @@ signal patch_applied
|
||||
signal store_update_required(store_url: String)
|
||||
|
||||
# Configuration - Update these URLs for your game
|
||||
const VERSION_MANIFEST_URL := "https://your-username.itch.io/tekton-local/data/version.json"
|
||||
const VERSION_MANIFEST_URL := "https://raw.githubusercontent.com/tekton-studios/tekton-updates/main/version.json"
|
||||
const ANDROID_STORE_URL := "https://play.google.com/store/apps/details?id=com.yourcompany.tekton"
|
||||
const IOS_STORE_URL := "https://apps.apple.com/app/tekton/id123456789"
|
||||
|
||||
@@ -22,8 +22,8 @@ const IOS_STORE_URL := "https://apps.apple.com/app/tekton/id123456789"
|
||||
enum Platform { WINDOWS, LINUX, MACOS, ANDROID, IOS, WEB }
|
||||
|
||||
# State
|
||||
var current_version: String = "0.9.0"
|
||||
var latest_version: String = "0.9.0"
|
||||
var current_version: String = "2.1.5"
|
||||
var latest_version: String = "2.1.5"
|
||||
var manifest_data: Dictionary = {}
|
||||
var http_request: HTTPRequest
|
||||
var download_request: HTTPRequest
|
||||
|
||||
+104
-114
@@ -1,123 +1,96 @@
|
||||
extends Control
|
||||
## Boot screen that handles update checking before launching the game
|
||||
## On mobile: Shows update UI and handles in-game patching
|
||||
## On desktop: Quick check, then proceeds (assumes launcher handles updates)
|
||||
## Shows changelog with pagination, checking patch, and resource loading
|
||||
|
||||
@onready var status_label := %StatusLabel as Label
|
||||
@onready var progress_container := %ProgressContainer as VBoxContainer
|
||||
@onready var progress_bar := %ProgressBar as ProgressBar
|
||||
@onready var progress_label := %ProgressLabel as Label
|
||||
@onready var button_container := %ButtonContainer as HBoxContainer
|
||||
@onready var update_button := %UpdateButton as Button
|
||||
@onready var skip_button := %SkipButton as Button
|
||||
@onready var store_button := %StoreButton as Button
|
||||
@onready var version_label := $VersionLabel as Label
|
||||
|
||||
@onready var changelog_panel := %ChangelogPanel
|
||||
@onready var changelog_richtext := %ChangelogRichText as RichTextLabel
|
||||
@onready var prev_button := %PrevButton as Button
|
||||
@onready var next_button := %NextButton as Button
|
||||
@onready var page_label := %PageLabel as Label
|
||||
|
||||
var update_manager: Node
|
||||
var update_info: Dictionary = {}
|
||||
var main_scene_path := "res://scenes/main.tscn" # Your main game scene
|
||||
var main_scene_path := "res://scenes/ui/login_screen.tscn"
|
||||
|
||||
# Changelog Pagination
|
||||
var changelog_data: Array = []
|
||||
var current_page: int = 0
|
||||
var is_loading_game: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
# Get or create the update manager
|
||||
update_manager = _get_update_manager()
|
||||
|
||||
# Connect signals
|
||||
update_manager.update_check_completed.connect(_on_update_check_completed)
|
||||
update_manager.update_check_failed.connect(_on_update_check_failed)
|
||||
update_manager.download_started.connect(_on_download_started)
|
||||
update_manager.download_progress.connect(_on_download_progress)
|
||||
update_manager.download_completed.connect(_on_download_completed)
|
||||
update_manager.download_failed.connect(_on_download_failed)
|
||||
update_manager.store_update_required.connect(_on_store_update_required)
|
||||
|
||||
# Connect buttons
|
||||
update_button.pressed.connect(_on_update_pressed)
|
||||
skip_button.pressed.connect(_on_skip_pressed)
|
||||
store_button.pressed.connect(_on_store_pressed)
|
||||
skip_button.pressed.connect(_begin_resource_load)
|
||||
prev_button.pressed.connect(_on_prev_pressed)
|
||||
next_button.pressed.connect(_on_next_pressed)
|
||||
|
||||
# Show current version
|
||||
version_label.text = "v" + update_manager.current_version
|
||||
changelog_panel.visible = false
|
||||
button_container.visible = false
|
||||
progress_bar.visible = false
|
||||
progress_label.visible = false
|
||||
|
||||
# Start update check after a brief delay
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
_check_for_updates()
|
||||
status_label.text = "Checking versions..."
|
||||
update_manager.check_for_updates()
|
||||
|
||||
func _get_update_manager() -> Node:
|
||||
# Try to get from autoload first
|
||||
if has_node("/root/GameUpdateManager"):
|
||||
return get_node("/root/GameUpdateManager")
|
||||
|
||||
# Otherwise, create instance
|
||||
var manager_script := load("res://scripts/managers/game_update_manager.gd")
|
||||
var manager: Node = manager_script.new()
|
||||
manager.name = "GameUpdateManager"
|
||||
get_tree().root.add_child(manager)
|
||||
return manager
|
||||
|
||||
func _check_for_updates() -> void:
|
||||
status_label.text = "Checking for updates..."
|
||||
progress_container.visible = false
|
||||
button_container.visible = false
|
||||
|
||||
# On desktop without launcher, skip update check and go straight to game
|
||||
if update_manager.is_desktop() and not update_manager._launcher_available():
|
||||
# Quick check but don't block
|
||||
update_manager.check_for_updates()
|
||||
# Auto-proceed after short delay if on desktop
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
if not update_info.get("has_update", false):
|
||||
_proceed_to_game()
|
||||
else:
|
||||
update_manager.check_for_updates()
|
||||
|
||||
func _on_update_check_completed(has_update: bool, info: Dictionary) -> void:
|
||||
update_info = info
|
||||
version_label.text = "v" + info.current_version
|
||||
|
||||
if has_update:
|
||||
if info.needs_store_update:
|
||||
status_label.text = "A required update is available.\nPlease update from the store."
|
||||
button_container.visible = true
|
||||
update_button.visible = false
|
||||
skip_button.visible = false
|
||||
store_button.visible = true
|
||||
elif info.can_patch:
|
||||
status_label.text = "Update available: v" + info.latest_version
|
||||
button_container.visible = true
|
||||
update_button.visible = true
|
||||
skip_button.visible = true
|
||||
store_button.visible = false
|
||||
else:
|
||||
# Desktop with launcher - just proceed
|
||||
status_label.text = "Update available via launcher"
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
_proceed_to_game()
|
||||
# Load changelog array specifically
|
||||
changelog_data = update_manager.manifest_data.get("releases", [])
|
||||
if changelog_data.size() > 0:
|
||||
changelog_panel.visible = true
|
||||
current_page = 0
|
||||
_update_pagination_ui()
|
||||
|
||||
if has_update and info.can_patch:
|
||||
status_label.text = "Update Available: v" + info.latest_version
|
||||
button_container.visible = true
|
||||
update_button.visible = true
|
||||
skip_button.visible = true
|
||||
skip_button.text = "Play without updating"
|
||||
else:
|
||||
status_label.text = "Game is up to date!"
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
_proceed_to_game()
|
||||
status_label.text = "Game up to date."
|
||||
_begin_resource_load()
|
||||
|
||||
func _on_update_check_failed(error: String) -> void:
|
||||
func _on_update_check_failed(_error: String) -> void:
|
||||
status_label.text = "Could not check for updates"
|
||||
print("[BootScreen] Update check failed: ", error)
|
||||
|
||||
# Allow playing anyway
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
_proceed_to_game()
|
||||
button_container.visible = true
|
||||
update_button.visible = false
|
||||
skip_button.visible = true
|
||||
skip_button.text = "Play Offline"
|
||||
|
||||
func _on_update_pressed() -> void:
|
||||
button_container.visible = false
|
||||
status_label.text = "Downloading update..."
|
||||
update_manager.download_update()
|
||||
|
||||
func _on_skip_pressed() -> void:
|
||||
_proceed_to_game()
|
||||
|
||||
func _on_store_pressed() -> void:
|
||||
update_manager.open_store_page()
|
||||
|
||||
func _on_download_started(_total_size: int) -> void:
|
||||
progress_container.visible = true
|
||||
func _on_download_started(total_size: int) -> void:
|
||||
progress_bar.visible = true
|
||||
progress_label.visible = true
|
||||
progress_bar.value = 0
|
||||
progress_label.text = "0%"
|
||||
|
||||
@@ -126,56 +99,73 @@ func _on_download_progress(_downloaded: int, _total: int, percentage: float) ->
|
||||
progress_label.text = "%.0f%%" % percentage
|
||||
|
||||
func _on_download_completed() -> void:
|
||||
status_label.text = "Update installed!"
|
||||
progress_container.visible = false
|
||||
|
||||
# Reload the game to apply changes
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
get_tree().reload_current_scene()
|
||||
status_label.text = "Update installed successfully!"
|
||||
progress_bar.visible = false
|
||||
progress_label.visible = false
|
||||
_begin_resource_load()
|
||||
|
||||
func _on_download_failed(error: String) -> void:
|
||||
status_label.text = "Update failed: " + error
|
||||
progress_container.visible = false
|
||||
progress_bar.visible = false
|
||||
progress_label.visible = false
|
||||
button_container.visible = true
|
||||
update_button.text = "Retry"
|
||||
|
||||
func _on_store_update_required(store_url: String) -> void:
|
||||
status_label.text = "Please update from the store"
|
||||
button_container.visible = true
|
||||
update_button.visible = false
|
||||
skip_button.visible = false
|
||||
store_button.visible = true
|
||||
|
||||
func _proceed_to_game() -> void:
|
||||
# Load any previously downloaded patches
|
||||
_load_existing_patches()
|
||||
func _begin_resource_load() -> void:
|
||||
button_container.visible = false
|
||||
status_label.text = "Loading resources..."
|
||||
progress_bar.visible = true
|
||||
progress_label.visible = true
|
||||
progress_bar.value = 0
|
||||
progress_label.text = "0%"
|
||||
|
||||
# Change to main game scene
|
||||
get_tree().change_scene_to_file(main_scene_path)
|
||||
is_loading_game = true
|
||||
ResourceLoader.load_threaded_request(main_scene_path)
|
||||
|
||||
func _load_existing_patches() -> void:
|
||||
# Load any PCK files from the patches directory
|
||||
var patches_dir := "user://patches/"
|
||||
var dir := DirAccess.open(patches_dir)
|
||||
if not dir:
|
||||
func _process(_delta: float) -> void:
|
||||
if is_loading_game:
|
||||
var progress := []
|
||||
var status := ResourceLoader.load_threaded_get_status(main_scene_path, progress)
|
||||
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
|
||||
var perc = progress[0] * 100.0
|
||||
progress_bar.value = perc
|
||||
progress_label.text = "%.0f%%" % perc
|
||||
elif status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
is_loading_game = false
|
||||
get_tree().change_scene_to_packed(ResourceLoader.load_threaded_get(main_scene_path))
|
||||
elif status == ResourceLoader.THREAD_LOAD_FAILED:
|
||||
is_loading_game = false
|
||||
status_label.text = "Error Loading Game"
|
||||
|
||||
# --- Pagination UI Functions ---
|
||||
func _update_pagination_ui():
|
||||
if changelog_data.size() == 0:
|
||||
changelog_richtext.text = "No changelog data available."
|
||||
prev_button.disabled = true
|
||||
next_button.disabled = true
|
||||
page_label.text = "0/0"
|
||||
return
|
||||
|
||||
var entry: Dictionary = changelog_data[current_page]
|
||||
var txt := "[font_size=20][b]Version " + entry.get("version", "Unknown") + "[/b][/font_size]\n[color=gray]" + entry.get("date", "") + "[/color]\n\n"
|
||||
|
||||
var patches: Array[String] = []
|
||||
dir.list_dir_begin()
|
||||
var patch_file := dir.get_next()
|
||||
while patch_file != "":
|
||||
if patch_file.ends_with(".pck"):
|
||||
patches.append(patch_file)
|
||||
patch_file = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
var changes: Array = entry.get("changelog", [])
|
||||
for change in changes:
|
||||
txt += "• " + change + "\n"
|
||||
|
||||
changelog_richtext.text = txt
|
||||
|
||||
# Sort patches by version (filename includes version)
|
||||
patches.sort()
|
||||
|
||||
# Load each patch in order
|
||||
for patch in patches:
|
||||
var patch_path := patches_dir + patch
|
||||
if ProjectSettings.load_resource_pack(patch_path, true):
|
||||
print("[BootScreen] Loaded patch: ", patch)
|
||||
else:
|
||||
push_warning("[BootScreen] Failed to load patch: ", patch)
|
||||
page_label.text = str(current_page + 1) + " / " + str(changelog_data.size())
|
||||
prev_button.disabled = (current_page == 0)
|
||||
next_button.disabled = (current_page >= changelog_data.size() - 1)
|
||||
|
||||
func _on_prev_pressed():
|
||||
if current_page > 0:
|
||||
current_page -= 1
|
||||
_update_pagination_ui()
|
||||
|
||||
func _on_next_pressed():
|
||||
if current_page < changelog_data.size() - 1:
|
||||
current_page += 1
|
||||
_update_pagination_ui()
|
||||
|
||||
Reference in New Issue
Block a user