From c8e5a4552953e492c2d002466d08e654809acf4d Mon Sep 17 00:00:00 2001 From: adtpdn Date: Wed, 22 Apr 2026 04:21:00 +0800 Subject: [PATCH] feat: patch system --- .github/workflows/deploy_patch.yml | 60 ++++ .../enhanced_gridmap/meshlibrary/default.tres | 2 +- assets/data/version.json | 15 + .../models/meshes/tiles_armagedon.glb.import | 2 +- assets/models/meshes/tiles_armagedon_a1.res | Bin 5064 -> 11552 bytes docs/MOBILE_UPDATES.md | 93 ----- export_presets.cfg | 6 +- launcher/.gdignore | 0 launcher/README.md | 124 ------- launcher/icon.svg | 27 -- launcher/icon.svg.import | 37 -- launcher/project.godot | 27 -- launcher/scenes/launcher.tscn | 197 ---------- launcher/scripts/config.gd | 73 ---- launcher/scripts/config.gd.uid | 1 - launcher/scripts/download_manager.gd | 133 ------- launcher/scripts/download_manager.gd.uid | 1 - launcher/scripts/launcher_main.gd | 338 ------------------ launcher/scripts/launcher_main.gd.uid | 1 - launcher/scripts/news_fetcher.gd | 70 ---- launcher/scripts/news_fetcher.gd.uid | 1 - launcher/scripts/update_applier.gd | 164 --------- launcher/scripts/update_applier.gd.uid | 1 - launcher/scripts/version_checker.gd | 142 -------- launcher/scripts/version_checker.gd.uid | 1 - launcher/server/version.json | 83 ----- project.godot | 4 +- scenes/boot_screen.tscn | 131 ------- scenes/ui/boot_screen.tscn | 120 +++++++ scripts/managers/game_update_manager.gd | 6 +- scripts/ui/boot_screen.gd | 218 ++++++----- tiles_armagedon_a1.res | Bin 11552 -> 0 bytes tools/build_patch.gd | 54 +++ tools/build_patch.gd.uid | 1 + tools/create_patch.ps1 | 77 ---- version.txt | 1 - 36 files changed, 364 insertions(+), 1847 deletions(-) create mode 100644 .github/workflows/deploy_patch.yml create mode 100644 assets/data/version.json delete mode 100644 docs/MOBILE_UPDATES.md delete mode 100644 launcher/.gdignore delete mode 100644 launcher/README.md delete mode 100644 launcher/icon.svg delete mode 100644 launcher/icon.svg.import delete mode 100644 launcher/project.godot delete mode 100644 launcher/scenes/launcher.tscn delete mode 100644 launcher/scripts/config.gd delete mode 100644 launcher/scripts/config.gd.uid delete mode 100644 launcher/scripts/download_manager.gd delete mode 100644 launcher/scripts/download_manager.gd.uid delete mode 100644 launcher/scripts/launcher_main.gd delete mode 100644 launcher/scripts/launcher_main.gd.uid delete mode 100644 launcher/scripts/news_fetcher.gd delete mode 100644 launcher/scripts/news_fetcher.gd.uid delete mode 100644 launcher/scripts/update_applier.gd delete mode 100644 launcher/scripts/update_applier.gd.uid delete mode 100644 launcher/scripts/version_checker.gd delete mode 100644 launcher/scripts/version_checker.gd.uid delete mode 100644 launcher/server/version.json delete mode 100644 scenes/boot_screen.tscn create mode 100644 scenes/ui/boot_screen.tscn delete mode 100644 tiles_armagedon_a1.res create mode 100644 tools/build_patch.gd create mode 100644 tools/build_patch.gd.uid delete mode 100644 tools/create_patch.ps1 delete mode 100644 version.txt diff --git a/.github/workflows/deploy_patch.yml b/.github/workflows/deploy_patch.yml new file mode 100644 index 0000000..4012920 --- /dev/null +++ b/.github/workflows/deploy_patch.yml @@ -0,0 +1,60 @@ +name: Build and Release Patch PCK + +on: + push: + branches: + - 'patch-release' + paths: + - 'scripts/**' + - 'scenes/**' + - 'assets/**' + - 'assets/data/version.json' + +jobs: + build-and-deploy-patch: + runs-on: ubuntu-latest + steps: + - name: Checkout Source Code + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Generate Changed Files List + run: | + git diff --name-only HEAD^ HEAD > changed_files.txt + echo "Files to patch:" + cat changed_files.txt + + - name: Setup Godot + uses: chickensoft-games/setup-godot@v1 + with: + version: '4.2.1' + use-dotnet: false + + - name: Run Build Patch Script + run: godot --headless -s tools/build_patch.gd + + # Push the patch files directly to the public repository structure! + - name: Push to Public Repository + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }} + with: + source_file: 'patch.pck' + destination_repo: '${{ github.actor }}/tekton-updates' + destination_folder: 'latest' + user_email: 'action@github.com' + user_name: 'PatchBot' + commit_message: '[AUTO] Pushed new patch.pck via CI' + + - name: Push Version Manifest to Public Repository + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }} + with: + source_file: 'assets/data/version.json' + destination_repo: '${{ github.actor }}/tekton-updates' + destination_folder: 'latest' + user_email: 'action@github.com' + user_name: 'PatchBot' + commit_message: '[AUTO] Pushed new version.json via CI' diff --git a/addons/enhanced_gridmap/meshlibrary/default.tres b/addons/enhanced_gridmap/meshlibrary/default.tres index 1f089fa..141a3c0 100644 --- a/addons/enhanced_gridmap/meshlibrary/default.tres +++ b/addons/enhanced_gridmap/meshlibrary/default.tres @@ -14,7 +14,7 @@ [ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"] [ext_resource type="BoxMesh" uid="uid://fy4bhoeii40c" path="res://addons/enhanced_gridmap/meshlibrary/tile_safe_zone.tres" id="10_uwjsj"] [ext_resource type="BoxMesh" uid="uid://b5cc3prem52r6" path="res://addons/enhanced_gridmap/meshlibrary/tile_freeze.tres" id="11_pgnbl"] -[ext_resource type="BoxMesh" uid="uid://dcjdwbffgtutt" path="res://addons/enhanced_gridmap/meshlibrary/tile_non_walkable.tres" id="11_uwjsj"] +[ext_resource type="BoxMesh" path="res://addons/enhanced_gridmap/meshlibrary/tile_non_walkable.tres" id="11_uwjsj"] [ext_resource type="Texture2D" uid="uid://cdnxwlysxnujd" path="res://assets/textures/tile_heart.png" id="12_heart_tex"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uxput"] diff --git a/assets/data/version.json b/assets/data/version.json new file mode 100644 index 0000000..3a657af --- /dev/null +++ b/assets/data/version.json @@ -0,0 +1,15 @@ +{ + "latest_version": "2.1.5", + "minimum_app_version": "2.1.0", + "releases": [ + { + "version": "2.1.5", + "date": "2026-04-20", + "pck_url": "https://raw.githubusercontent.com/tekton-studios/tekton-updates/main/patch.pck", + "changelog": [ + "Initial release of the new patching system.", + "Added boot screen paginator." + ] + } + ] +} diff --git a/assets/models/meshes/tiles_armagedon.glb.import b/assets/models/meshes/tiles_armagedon.glb.import index 1878172..3bb1b76 100644 --- a/assets/models/meshes/tiles_armagedon.glb.import +++ b/assets/models/meshes/tiles_armagedon.glb.import @@ -76,7 +76,7 @@ _subresources={ "lods/normal_merge_angle": 60.0, "lods/normal_split_angle": 25.0, "save_to_file/enabled": true, -"save_to_file/fallback_path": "res://tiles_armagedon_a1.res", +"save_to_file/fallback_path": "res://assets/models/meshes/tiles_armagedon_a1.res", "save_to_file/path": "uid://dc8xogqvnqor7" }, "tiles_armagedon_Cube_012": { diff --git a/assets/models/meshes/tiles_armagedon_a1.res b/assets/models/meshes/tiles_armagedon_a1.res index 5fc22678e02d5dd479988fb6b84f8ec89d78e426..db713e88672f701c858f670eb2d521335e777f4b 100644 GIT binary patch literal 11552 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 literal 5064 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�#trSsj>Im=ZUJ z7z8gVk|kSAWN=zt*A>+ZODg#|0}LqDl0at)h3}mf_ZCMY=5v^4GC1F<4T|zH6GlN z(Zogv&Ic87URL`n;HsLJ!iJO1LX9*!BOz6Nutl=)+17xwJ1PQ)R|UhC8w*y`JV`jR zasq)Ts8O*p%!r7Hj7UMhIu<@= zd6z8527?_OoPOrw1_Y-ulta-Tu|P>2*`7q_tvYFY@Om?erCCXhN&C8_q+E%kzv{!@ zxF{*_C^LquG+8Ye3dvP3og`n9kd^575~A@+X)0@UBtL3~+9|BMVcFAs%;}z-?v3<~ zRy9R!w~%DLi5eG(;==MKQnBn&wt{xNdzilDDs69z4yGr=Wy^MA&RhmkW66To6ofRq zJgn2QrO^eT9ArT! zYG`)?=#)-yv0QR2Bi;fPY~0FR+wSnKe_UswIluB$=O*a1002WYPrO2aLI&v%R4BDC z{a`i>B{l%MR>TuQKpNK?$6zpdd>ez^1`k}k3$}UnXiT+ZIh_Am8Kz{gaS1{7CcbF6(<__nzKEkdpA4y_3$+B!D1t$S80V@GIhu-EnhaTrW*Zz5&YtQqWCttnw z%r9R(^V?Uy{LZ&u{=fPfi?gc!OP}E5E3gz-lvme(sFGP2$~y@6s5A|sS3Z}%f-w_>8kdko>nE**%TYxh@8~RC0KnW(os*d z7V2vdl{eBtn$lGbM(s7QrnWYk`2XcU_h2$u95p6)g{!v6Wbps$f1m%$le4@I&Pv<{mky)S_>@crmYj6h>q~=y@raspBFr7zcc@4e zdv>tntGxG)(gfgJ-a9lP|GxCbbW!wRRAa&g7Qzz^5P`TbAktP?<}(_ej}>NJvw`bwRKOXr{*5j>aKB77wM| zrX>ET5ZsF@rfe{SE!NI|0?rl%d;Y*5RwO_G*g`%uE5js3J5n(D2a;Kl0E@7Nep1>s zDOyrAJ^`}U?Y*MO=VoP?go2f!5V0NkFaFfYd7-)+vUeU6F-wGyVGHZ#v}<>Hi$8gR zUa0oAA_WoK`!Cx@ykeH@UAF&ZgPY~ptBPEORkc;(e>A|Y+5ZJj24ih$P5poVJWl|Q z8edhB|J#hQz*))v%KtmR0#|K~$zUu7H`zO6az{*CoF)kiNq_0d;Pz4ce;pT{2O&SUSq&7ptZ=E_SS z{qjoYn@=v^JoCyg&-|ZOlO;uZpVBEc852$U+^p9{jG*#=MwDf-`JGX*iM2?U*PtEc zImsaMS2iQE3_;I{Pl}$1KrJlf!c8(1rxay;pUN}h!jEN2HKH?G4G75wYmuOFk}}Gb z+s;>cN-#EmMTkN6%rG{A$DFxSYiAx$R)|jFxyGfmzQsB%@IMt#m>%R^SQ9ubarnjl z6=XMJZmFMMdFG=}CQrT1o6SFOq=$Za=cNpuHW2R~kt|*_uLenA&p8`VYg20-)OgCW z*!+6by5cgW8W9{44Z5VN)tNI0te_|c2}1*kg^P!yvn#2{LCb@pnJNq6R>j_%PwL{p z-YEK#+&}}|`zbuwV(%*s4Bz&yV+MwOI5Zpi`0xk9HL2N~0GZ(%G=~n2Uy69|G&7>d>(q`lXo8a_e-C7(G^2t=!%)$2Uik`zBKpm zOG_wvaMcxa|Ln%T^d1Dev97oZm#&!X2(0THfa<6rAw-gljEIOxQjjDafP$%_nYsZI zphr>U5J`-PB*i5K_9GpKXj(S_R))||O0lKG6a7$09F`t0eR5{o;&Yj3W{HPh0EtuS zt^#kwYu&QmZXPY0XRN(UZr^W5Su|@BIL1(J^aZ?#S9gBf_|`l=&9~~yKX&mEW~+Cz zv1@f8@fjrdyrlbM^SrTiqF*TV>NjwbWhsAjF6h;w9im+R&O8j- zRMbI7riI3R`P-kYAjAz_jyXb=g@1Iw53?@A)h+IyG${Yoi37X6*}k&bZ_9})p1{`( z2Ykr(LN?t-`U)p^y=FP>t2?J`qTkq`xy4cJJy!TZbbYZ-3jheJ;{^-EmZs? Project Settings > Application > Run -- Set `Main Scene` to `res://scenes/boot_screen.tscn` - -Alternatively, keep your current main scene and call the update manager manually. - -### 2. Configure URLs - -Edit `scripts/managers/game_update_manager.gd`: - -```gdscript -const VERSION_MANIFEST_URL := "https://your-server.com/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" -``` - -### 3. Using minimum_app_version - -In `version.json`, set `minimum_app_version` to force store updates: - -```json -{ - "latest_version": "1.2.0", - "minimum_app_version": "1.1.0" -} -``` - -If a user has version 1.0.0, they'll be directed to the store since in-app patching can't upgrade from below the minimum. - -## Creating Mobile Patches - -1. Export your game changes as a PCK file from Godot -2. Upload to your hosting (same as desktop patches) -3. Update `version.json` with `pck_android` and `pck_ios` entries - -```json -"pck_android": { - "url": "https://your-server.com/patches/tekton-1.2.0-android.pck", - "size": 5242880, - "checksum_md5": "abc123..." -} -``` - -## Limitations - -- **Cannot update native code** - Engine/plugin changes require store update -- **Cannot update main executable** - Only assets and GDScript -- **Storage space** - Patches accumulate in user://patches/ -- **iOS App Store rules** - Be careful about downloading executable content diff --git a/export_presets.cfg b/export_presets.cfg index b1726fd..06a0d24 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -8,7 +8,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="build/tekton_armageddon_v2.1.4.exe" +export_path="build/tekton_armageddon_v2.1.5.exe" patches=PackedStringArray() patch_delta_encoding=false patch_delta_compression_level_zstd=19 @@ -80,7 +80,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="build/tekton-dash-armageddon-v.2.1.4.apk" +export_path="build/tekton-dash-armageddon-v.2.1.5.apk" patches=PackedStringArray() patch_delta_encoding=false patch_delta_compression_level_zstd=19 @@ -111,7 +111,7 @@ architectures/arm64-v8a=true architectures/x86=false architectures/x86_64=false version/code=2 -version/name="2.1.2" +version/name="2.1.5" package/unique_name="com.danchiego.$genname" package/name="Tekton Dash Armageddon" package/signed=true diff --git a/launcher/.gdignore b/launcher/.gdignore deleted file mode 100644 index e69de29..0000000 diff --git a/launcher/README.md b/launcher/README.md deleted file mode 100644 index 7b9b29b..0000000 --- a/launcher/README.md +++ /dev/null @@ -1,124 +0,0 @@ -# Tekton Launcher - -A custom game launcher for **tekton-local** with auto-update functionality via itch.io. - -## Features - -- **Version Checking** - Automatically checks for game updates on launch -- **Progress Tracking** - Visual download progress with percentage -- **Checksum Verification** - MD5/SHA256 validation for downloaded files -- **Backup System** - Automatic backup before updates with rollback support -- **News Feed** - Displays devlog and announcements from itch.io -- **Changelog Display** - Shows what's new in each version - -## Project Structure - -``` -launcher/ -├── project.godot # Godot 4.4 project file -├── icon.svg # Launcher icon -├── scenes/ -│ └── launcher.tscn # Main launcher scene -├── scripts/ -│ ├── config.gd # Global configuration (URLs, colors) -│ ├── launcher_main.gd # Main orchestration script -│ ├── version_checker.gd # Version comparison and manifest fetching -│ ├── download_manager.gd # HTTP downloads with progress -│ ├── update_applier.gd # Backup, install, and rollback -│ └── news_fetcher.gd # News/devlog fetching -└── server/ - └── version.json # Example version manifest -``` - -## Setup - -### 1. Configure itch.io URLs - -Edit `scripts/config.gd` and update: - -```gdscript -const ITCH_GAME_URL := "https://your-username.itch.io/tekton-local" -const VERSION_MANIFEST_URL := "https://your-username.itch.io/tekton-local/data/version.json" -``` - -### 2. Host version.json on itch.io - -Upload the `server/version.json` file to itch.io alongside your game files. Update it whenever you release a new version. - -### 3. Export the Launcher - -1. Open `launcher/project.godot` in Godot 4.4 -2. Go to **Project > Export...** -3. Add a Windows Desktop preset -4. Export as `TektonLauncher.exe` - -### 4. Creating Game Patches - -Use the PowerShell script in the main project: - -```powershell -.\tools\create_patch.ps1 -Version "1.0.0" -``` - -This will: -- Update `version.txt` in your game -- Generate checksums for the PCK file -- Create a JSON snippet to add to `version.json` - -## Workflow for Releasing Updates - -1. Make changes to the main game -2. Update `version.txt` with the new version number -3. Export the game as PCK only from Godot -4. Run `create_patch.ps1` to generate checksums -5. Upload the new PCK to itch.io -6. Update `version.json` with the new release entry -7. Upload updated `version.json` to itch.io - -Players will see the update next time they launch the game! - -## UI Theme - -The launcher features a cyberpunk-inspired dark theme: -- Primary: Cyan (#00d4ff) -- Secondary: Magenta (#ff00aa) -- Background: Dark blue-black (#0a0a1a) - -Customize colors in `scripts/config.gd`. - -## Cross-Platform Support - -The launcher automatically detects the operating system and: - -| Platform | Executable | PCK File | Launch Method | -|----------|------------|----------|---------------| -| Windows | `tekton-local.exe` | `tekton-local-windows.pck` | Direct process | -| Linux | `tekton-local.x86_64` | `tekton-local-linux.pck` | chmod +x, then launch | -| macOS | `tekton-local.app` | `tekton-local-macos.pck` | `open -a` command | - -### Multi-Platform Releases - -When releasing for multiple platforms, use platform-specific keys in `version.json`: - -```json -{ - "version": "1.0.0", - "pck_windows": { - "url": "https://..../tekton-local-1.0.0-windows.pck", - "size": 52428800, - "checksum_md5": "..." - }, - "pck_linux": { - "url": "https://..../tekton-local-1.0.0-linux.pck", - "size": 52428800, - "checksum_md5": "..." - }, - "pck_macos": { - "url": "https://..../tekton-local-1.0.0-macos.pck", - "size": 52428800, - "checksum_md5": "..." - } -} -``` - -The launcher will automatically download the correct PCK for the current platform. diff --git a/launcher/icon.svg b/launcher/icon.svg deleted file mode 100644 index d10f9ee..0000000 --- a/launcher/icon.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/launcher/icon.svg.import b/launcher/icon.svg.import deleted file mode 100644 index ed6ddf0..0000000 --- a/launcher/icon.svg.import +++ /dev/null @@ -1,37 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://b048nsl527nah" -path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://icon.svg" -dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=false -editor/convert_colors_with_editor_theme=false diff --git a/launcher/project.godot b/launcher/project.godot deleted file mode 100644 index 0a24675..0000000 --- a/launcher/project.godot +++ /dev/null @@ -1,27 +0,0 @@ -; Engine configuration file. -; It's best edited using the editor UI and not directly, -; since the parameters that go here are not all obvious. -; -; Format: -; [section] ; section goes between [] -; param=value ; assign values to parameters - -config_version=5 - -[application] - -config/name="Tekton Launcher" -run/main_scene="res://scenes/launcher.tscn" -config/features=PackedStringArray("4.4", "Forward Plus") -config/icon="res://icon.svg" - -[autoload] - -Config="*res://scripts/config.gd" - -[display] - -window/size/viewport_width=900 -window/size/viewport_height=600 -window/size/resizable=false -window/stretch/mode="viewport" diff --git a/launcher/scenes/launcher.tscn b/launcher/scenes/launcher.tscn deleted file mode 100644 index d47e713..0000000 --- a/launcher/scenes/launcher.tscn +++ /dev/null @@ -1,197 +0,0 @@ -[gd_scene load_steps=12 format=3 uid="uid://lwtn5sbr5jr2"] - -[ext_resource type="Script" uid="uid://cvstgaigwt0sj" path="res://scripts/launcher_main.gd" id="1"] -[ext_resource type="Script" uid="uid://ptdurqksg1sv" path="res://scripts/version_checker.gd" id="2"] -[ext_resource type="Script" uid="uid://cer12vb230kfw" path="res://scripts/download_manager.gd" id="3"] -[ext_resource type="Script" uid="uid://n2ptlbblexo6" path="res://scripts/update_applier.gd" id="4"] -[ext_resource type="Script" uid="uid://coer7l074xm6x" path="res://scripts/news_fetcher.gd" id="5"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_titlebar"] -bg_color = Color(0.059, 0.059, 0.118, 1) -corner_radius_top_left = 12 -corner_radius_top_right = 12 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tab_panel"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.059, 0.059, 0.118, 1) -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_progress_bg"] -bg_color = Color(0.1, 0.1, 0.2, 1) -corner_radius_top_left = 4 -corner_radius_top_right = 4 -corner_radius_bottom_right = 4 -corner_radius_bottom_left = 4 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_progress_fill"] -bg_color = Color(0, 0.831, 1, 1) -corner_radius_top_left = 4 -corner_radius_top_right = 4 -corner_radius_bottom_right = 4 -corner_radius_bottom_left = 4 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_hover"] -bg_color = Color(0.2, 0.9, 1, 1) -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button"] -bg_color = Color(0, 0.831, 1, 1) -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[node name="Launcher" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1") - -[node name="Background" type="ColorRect" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -color = Color(0.039, 0.039, 0.102, 1) - -[node name="VersionChecker" type="Node" parent="."] -script = ExtResource("2") - -[node name="DownloadManager" type="Node" parent="."] -script = ExtResource("3") - -[node name="UpdateApplier" type="Node" parent="."] -script = ExtResource("4") - -[node name="NewsFetcher" type="Node" parent="."] -script = ExtResource("5") - -[node name="MainPanel" type="VBoxContainer" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 20.0 -offset_top = 20.0 -offset_right = -20.0 -offset_bottom = -20.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="TitleBar" type="PanelContainer" parent="MainPanel"] -custom_minimum_size = Vector2(0, 60) -layout_mode = 2 -size_flags_vertical = 0 -theme_override_styles/panel = SubResource("StyleBoxFlat_titlebar") - -[node name="HBoxContainer" type="HBoxContainer" parent="MainPanel/TitleBar"] -layout_mode = 2 - -[node name="TitleLabel" type="Label" parent="MainPanel/TitleBar/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(0, 0.831, 1, 1) -theme_override_font_sizes/font_size = 24 -text = "TEKTON LAUNCHER" - -[node name="VersionLabel" type="Label" parent="MainPanel/TitleBar"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.533, 0.533, 0.6, 1) -text = "v0.0.0" -horizontal_alignment = 2 - -[node name="ContentContainer" type="VBoxContainer" parent="MainPanel"] -layout_mode = 2 -size_flags_vertical = 3 -alignment = 1 - -[node name="TabContainer" type="TabContainer" parent="MainPanel/ContentContainer"] -layout_mode = 2 -size_flags_vertical = 3 -theme_override_styles/panel = SubResource("StyleBoxFlat_tab_panel") -current_tab = 0 - -[node name="News" type="Control" parent="MainPanel/ContentContainer/TabContainer"] -layout_mode = 2 -metadata/_tab_index = 0 - -[node name="ScrollContainer" type="ScrollContainer" parent="MainPanel/ContentContainer/TabContainer/News"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="NewsVBox" type="VBoxContainer" parent="MainPanel/ContentContainer/TabContainer/News/ScrollContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="Changelog" type="Control" parent="MainPanel/ContentContainer/TabContainer"] -visible = false -layout_mode = 2 -metadata/_tab_index = 1 - -[node name="ScrollContainer" type="ScrollContainer" parent="MainPanel/ContentContainer/TabContainer/Changelog"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="ChangelogVBox" type="VBoxContainer" parent="MainPanel/ContentContainer/TabContainer/Changelog/ScrollContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="BottomBar" type="HBoxContainer" parent="MainPanel/ContentContainer"] -custom_minimum_size = Vector2(0, 80) -layout_mode = 2 -alignment = 2 - -[node name="StatusLabel" type="Label" parent="MainPanel/ContentContainer/BottomBar"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(0.533, 0.533, 0.6, 1) -text = "Checking for updates..." - -[node name="ProgressContainer" type="VBoxContainer" parent="MainPanel/ContentContainer/BottomBar"] -visible = false -custom_minimum_size = Vector2(200, 0) -layout_mode = 2 - -[node name="ProgressBar" type="ProgressBar" parent="MainPanel/ContentContainer/BottomBar/ProgressContainer"] -custom_minimum_size = Vector2(200, 20) -layout_mode = 2 -theme_override_styles/background = SubResource("StyleBoxFlat_progress_bg") -theme_override_styles/fill = SubResource("StyleBoxFlat_progress_fill") -show_percentage = false - -[node name="ProgressLabel" type="Label" parent="MainPanel/ContentContainer/BottomBar/ProgressContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.533, 0.533, 0.6, 1) -text = "0%" -horizontal_alignment = 1 - -[node name="PlayButton" type="Button" parent="MainPanel/ContentContainer/BottomBar"] -custom_minimum_size = Vector2(160, 50) -layout_mode = 2 -theme_override_colors/font_color = Color(0.039, 0.039, 0.102, 1) -theme_override_font_sizes/font_size = 18 -theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover") -theme_override_styles/normal = SubResource("StyleBoxFlat_button") -text = "► PLAY" diff --git a/launcher/scripts/config.gd b/launcher/scripts/config.gd deleted file mode 100644 index 7368af8..0000000 --- a/launcher/scripts/config.gd +++ /dev/null @@ -1,73 +0,0 @@ -extends Node - -## Global configuration for the Tekton Launcher - -# itch.io Configuration -const ITCH_GAME_URL := "https://your-username.itch.io/tekton-local" -const ITCH_DEVLOG_URL := "https://itch.io/api/1/your-key/game/your-game-id/devlog" # Replace with actual - -# Version manifest URL (hosted on itch.io or alongside game files) -const VERSION_MANIFEST_URL := "https://your-username.itch.io/tekton-local/data/version.json" - -# Local paths (shared across platforms) -const GAME_DIRECTORY := "user://game/" -const LOCAL_VERSION_FILE := "user://version.txt" -const BACKUP_DIRECTORY := "user://backup/" - -# Platform detection -enum Platform {WINDOWS, LINUX, MACOS} - -static func get_current_platform() -> Platform: - var os_name := OS.get_name() - match os_name: - "Windows": - return Platform.WINDOWS - "Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD": - return Platform.LINUX - "macOS": - return Platform.MACOS - _: - push_warning("Unknown platform: " + os_name + ", defaulting to Linux") - return Platform.LINUX - -static func get_game_executable() -> String: - match get_current_platform(): - Platform.WINDOWS: - return "tekton-local.exe" - Platform.LINUX: - return "tekton-local.x86_64" - Platform.MACOS: - return "tekton-local.app" - return "tekton-local" - -static func get_game_pck() -> String: - # PCK is the same across platforms, but we include platform in name for clarity - match get_current_platform(): - Platform.WINDOWS: - return "tekton-local-windows.pck" - Platform.LINUX: - return "tekton-local-linux.pck" - Platform.MACOS: - return "tekton-local-macos.pck" - return "tekton-local.pck" - -static func get_platform_name() -> String: - match get_current_platform(): - Platform.WINDOWS: - return "windows" - Platform.LINUX: - return "linux" - Platform.MACOS: - return "macos" - return "unknown" - -# UI Colors (Cyberpunk/Tech theme matching game aesthetic) -const COLOR_PRIMARY := Color("#00d4ff") # Cyan -const COLOR_SECONDARY := Color("#ff00aa") # Magenta -const COLOR_BACKGROUND := Color("#0a0a1a") # Dark blue-black -const COLOR_SURFACE := Color("#141428") # Slightly lighter -const COLOR_SUCCESS := Color("#00ff88") # Green -const COLOR_WARNING := Color("#ffaa00") # Orange -const COLOR_ERROR := Color("#ff4444") # Red -const COLOR_TEXT := Color("#ffffff") # White -const COLOR_TEXT_DIM := Color("#888899") # Dim text diff --git a/launcher/scripts/config.gd.uid b/launcher/scripts/config.gd.uid deleted file mode 100644 index 42dfe2d..0000000 --- a/launcher/scripts/config.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://huq4exk8psgu diff --git a/launcher/scripts/download_manager.gd b/launcher/scripts/download_manager.gd deleted file mode 100644 index c0080b9..0000000 --- a/launcher/scripts/download_manager.gd +++ /dev/null @@ -1,133 +0,0 @@ -extends Node -class_name DownloadManager -## Handles downloading game files with progress tracking and checksum verification - -signal download_started(file_name: String, total_size: int) -signal download_progress(downloaded: int, total: int, percentage: float) -signal download_completed(file_path: String) -signal download_failed(error: String) - -var http_request: HTTPRequest -var download_path: String = "" -var expected_checksum_md5: String = "" -var expected_checksum_sha256: String = "" -var total_size: int = 0 -var is_downloading: bool = false - -func _ready() -> void: - http_request = HTTPRequest.new() - add_child(http_request) - http_request.request_completed.connect(_on_download_completed) - http_request.download_file = "" # We'll set this per download - - # Ensure directories exist - _ensure_directories() - -func _ensure_directories() -> void: - var dir := DirAccess.open("user://") - if dir: - if not dir.dir_exists("game"): - dir.make_dir("game") - if not dir.dir_exists("backup"): - dir.make_dir("backup") - if not dir.dir_exists("temp"): - dir.make_dir("temp") - -func _process(_delta: float) -> void: - if is_downloading and http_request: - var downloaded := http_request.get_downloaded_bytes() - var body_size := http_request.get_body_size() - - if body_size > 0: - var percentage := (float(downloaded) / float(body_size)) * 100.0 - emit_signal("download_progress", downloaded, body_size, percentage) - -func download_file(url: String, file_name: String, size: int = 0, md5: String = "", sha256: String = "") -> void: - if is_downloading: - emit_signal("download_failed", "Download already in progress") - return - - download_path = "user://temp/" + file_name - expected_checksum_md5 = md5 - expected_checksum_sha256 = sha256 - total_size = size - is_downloading = true - - # Set download file path - http_request.download_file = download_path - - print("[DownloadManager] Starting download: ", url) - print("[DownloadManager] Saving to: ", download_path) - - emit_signal("download_started", file_name, size) - - var error := http_request.request(url) - if error != OK: - is_downloading = false - emit_signal("download_failed", "Failed to initiate download: " + str(error)) - -func _on_download_completed(result: int, response_code: int, _headers: PackedStringArray, _body: PackedByteArray) -> void: - is_downloading = false - - if result != HTTPRequest.RESULT_SUCCESS: - emit_signal("download_failed", "Download failed with result: " + str(result)) - return - - if response_code != 200: - emit_signal("download_failed", "Server returned error: " + str(response_code)) - return - - # Verify checksum if provided - if expected_checksum_md5 != "" or expected_checksum_sha256 != "": - if not _verify_checksum(): - emit_signal("download_failed", "Checksum verification failed - file may be corrupted") - # Delete corrupted file - var dir := DirAccess.open("user://temp/") - if dir: - dir.remove(download_path.get_file()) - return - - print("[DownloadManager] Download completed: ", download_path) - emit_signal("download_completed", download_path) - -func _verify_checksum() -> bool: - ## Verify the downloaded file's checksum - var file := FileAccess.open(download_path, FileAccess.READ) - if not file: - print("[DownloadManager] Could not open file for checksum verification") - return false - - var content := file.get_buffer(file.get_length()) - file.close() - - # Verify MD5 if provided - if expected_checksum_md5 != "": - var ctx := HashingContext.new() - ctx.start(HashingContext.HASH_MD5) - ctx.update(content) - var calculated_md5 := ctx.finish().hex_encode() - - if calculated_md5.to_lower() != expected_checksum_md5.to_lower(): - print("[DownloadManager] MD5 mismatch! Expected: ", expected_checksum_md5, " Got: ", calculated_md5) - return false - print("[DownloadManager] MD5 checksum verified") - - # Verify SHA256 if provided - if expected_checksum_sha256 != "": - var ctx := HashingContext.new() - ctx.start(HashingContext.HASH_SHA256) - ctx.update(content) - var calculated_sha256 := ctx.finish().hex_encode() - - if calculated_sha256.to_lower() != expected_checksum_sha256.to_lower(): - print("[DownloadManager] SHA256 mismatch! Expected: ", expected_checksum_sha256, " Got: ", calculated_sha256) - return false - print("[DownloadManager] SHA256 checksum verified") - - return true - -func cancel_download() -> void: - if is_downloading: - http_request.cancel_request() - is_downloading = false - print("[DownloadManager] Download cancelled") diff --git a/launcher/scripts/download_manager.gd.uid b/launcher/scripts/download_manager.gd.uid deleted file mode 100644 index 1f52110..0000000 --- a/launcher/scripts/download_manager.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cer12vb230kfw diff --git a/launcher/scripts/launcher_main.gd b/launcher/scripts/launcher_main.gd deleted file mode 100644 index 92e7b36..0000000 --- a/launcher/scripts/launcher_main.gd +++ /dev/null @@ -1,338 +0,0 @@ -extends Control -## Main launcher scene script - orchestrates all launcher functionality - -const Config = preload("config.gd") -const VersionChecker = preload("version_checker.gd") -const DownloadManager = preload("download_manager.gd") -const UpdateApplier = preload("update_applier.gd") -const NewsFetcher = preload("news_fetcher.gd") - -# 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) diff --git a/launcher/scripts/launcher_main.gd.uid b/launcher/scripts/launcher_main.gd.uid deleted file mode 100644 index 9a9aeed..0000000 --- a/launcher/scripts/launcher_main.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cvstgaigwt0sj diff --git a/launcher/scripts/news_fetcher.gd b/launcher/scripts/news_fetcher.gd deleted file mode 100644 index 983baa4..0000000 --- a/launcher/scripts/news_fetcher.gd +++ /dev/null @@ -1,70 +0,0 @@ -extends Node -class_name NewsFetcher - -const Config = preload("config.gd") -## Fetches news/devlog entries from itch.io - -signal news_fetched(news_items: Array) -signal news_fetch_failed(error: String) - -var http_request: HTTPRequest - -# itch.io doesn't have a public devlog API, so we'll fetch from a custom endpoint -# You can host this JSON alongside your game files on itch.io - -func _ready() -> void: - http_request = HTTPRequest.new() - add_child(http_request) - http_request.request_completed.connect(_on_request_completed) - -func fetch_news() -> void: - # Fetch from the version manifest which includes news - print("[NewsFetcher] Fetching news from manifest...") - var error := http_request.request(Config.VERSION_MANIFEST_URL) - if error != OK: - emit_signal("news_fetch_failed", "Failed to initiate news request") - -func _on_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void: - if result != HTTPRequest.RESULT_SUCCESS: - emit_signal("news_fetch_failed", "Network error fetching news") - return - - if response_code != 200: - emit_signal("news_fetch_failed", "Server returned error: " + str(response_code)) - return - - var json := JSON.new() - var parse_result := json.parse(body.get_string_from_utf8()) - if parse_result != OK: - emit_signal("news_fetch_failed", "Failed to parse news data") - return - - var data: Dictionary = json.data - var news_items: Array = data.get("news", []) - - # Also include recent releases as news items - var releases: Array = data.get("releases", []) - for release in releases: - news_items.append({ - "title": "Version " + release.get("version", "?") + " Released!", - "date": release.get("date", ""), - "content": _format_changelog(release.get("changelog", [])), - "type": "release" - }) - - # Sort by date descending - news_items.sort_custom(_sort_by_date) - - print("[NewsFetcher] Fetched ", news_items.size(), " news items") - emit_signal("news_fetched", news_items) - -func _format_changelog(changelog: Array) -> String: - var text := "" - for item in changelog: - text += "• " + str(item) + "\n" - return text.strip_edges() - -func _sort_by_date(a: Dictionary, b: Dictionary) -> bool: - var date_a: String = a.get("date", "") - var date_b: String = b.get("date", "") - return date_a > date_b # Descending order diff --git a/launcher/scripts/news_fetcher.gd.uid b/launcher/scripts/news_fetcher.gd.uid deleted file mode 100644 index 4474dc2..0000000 --- a/launcher/scripts/news_fetcher.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://coer7l074xm6x diff --git a/launcher/scripts/update_applier.gd b/launcher/scripts/update_applier.gd deleted file mode 100644 index 7f77e88..0000000 --- a/launcher/scripts/update_applier.gd +++ /dev/null @@ -1,164 +0,0 @@ -extends Node -class_name UpdateApplier - -const Config = preload("config.gd") -## Handles applying downloaded updates: backup, replace, rollback - -signal update_started -signal update_progress(step: String, percentage: float) -signal update_completed -signal update_failed(error: String) -signal rollback_completed - -const BACKUP_COUNT := 2 # Keep this many backup versions - -func apply_update(downloaded_pck_path: String, version: String) -> void: - emit_signal("update_started") - - var game_dir := Config.GAME_DIRECTORY - var pck_name := Config.get_game_pck() - var target_pck := game_dir + pck_name - var backup_dir := Config.BACKUP_DIRECTORY - - # Step 1: Ensure game directory exists - emit_signal("update_progress", "Preparing directories...", 10.0) - if not _ensure_directory(game_dir): - emit_signal("update_failed", "Failed to create game directory") - return - - if not _ensure_directory(backup_dir): - emit_signal("update_failed", "Failed to create backup directory") - return - - # Step 2: Backup existing PCK if it exists - emit_signal("update_progress", "Backing up current version...", 30.0) - if FileAccess.file_exists(target_pck): - var backup_name := pck_name.get_basename() + "_backup_" + Time.get_datetime_string_from_system().replace(":", "-") + ".pck" - var backup_path := backup_dir + backup_name - - if not _copy_file(target_pck, backup_path): - emit_signal("update_failed", "Failed to backup current version") - return - - # Clean up old backups - _cleanup_old_backups(backup_dir, BACKUP_COUNT) - - # Step 3: Move downloaded file to game directory - emit_signal("update_progress", "Installing update...", 60.0) - - # Delete old PCK first - if FileAccess.file_exists(target_pck): - var dir := DirAccess.open(game_dir) - if dir: - var err := dir.remove(pck_name) - if err != OK: - emit_signal("update_failed", "Failed to remove old game file") - return - - # Copy new PCK - if not _copy_file(downloaded_pck_path, target_pck): - emit_signal("update_failed", "Failed to install update") - return - - # Step 4: Copy executable if needed (first install) - emit_signal("update_progress", "Finalizing...", 80.0) - - # Clean up temp file - var temp_dir := DirAccess.open("user://temp/") - if temp_dir: - temp_dir.remove(downloaded_pck_path.get_file()) - - emit_signal("update_progress", "Update complete!", 100.0) - emit_signal("update_completed") - -func _ensure_directory(path: String) -> bool: - var dir := DirAccess.open("user://") - if dir: - var relative_path := path.replace("user://", "") - if not dir.dir_exists(relative_path): - return dir.make_dir_recursive(relative_path) == OK - return true - return false - -func _copy_file(from: String, to: String) -> bool: - var source := FileAccess.open(from, FileAccess.READ) - if not source: - push_error("[UpdateApplier] Cannot open source file: " + from) - return false - - var dest := FileAccess.open(to, FileAccess.WRITE) - if not dest: - source.close() - push_error("[UpdateApplier] Cannot open destination file: " + to) - return false - - # Copy in chunks for large files - const CHUNK_SIZE := 1024 * 1024 # 1MB chunks - while source.get_position() < source.get_length(): - var chunk := source.get_buffer(CHUNK_SIZE) - dest.store_buffer(chunk) - - source.close() - dest.close() - - print("[UpdateApplier] Copied: ", from, " -> ", to) - return true - -func _cleanup_old_backups(backup_dir: String, keep_count: int) -> void: - var dir := DirAccess.open(backup_dir) - if not dir: - return - - var backups: Array[String] = [] - dir.list_dir_begin() - var file := dir.get_next() - while file != "": - if file.ends_with(".pck") and "backup" in file: - backups.append(file) - file = dir.get_next() - dir.list_dir_end() - - # Sort by name (which includes timestamp) - backups.sort() - - # Remove oldest backups if we have too many - while backups.size() > keep_count: - var old_backup = backups.pop_front() - dir.remove(old_backup) - print("[UpdateApplier] Removed old backup: ", old_backup) - -func rollback_to_backup() -> bool: - ## Rollback to the most recent backup - var backup_dir := Config.BACKUP_DIRECTORY - var game_dir := Config.GAME_DIRECTORY - var pck_name := Config.get_game_pck() - - var dir := DirAccess.open(backup_dir) - if not dir: - push_error("[UpdateApplier] Cannot access backup directory") - return false - - # Find the most recent backup - var backups: Array[String] = [] - dir.list_dir_begin() - var file := dir.get_next() - while file != "": - if file.ends_with(".pck") and "backup" in file: - backups.append(file) - file = dir.get_next() - dir.list_dir_end() - - if backups.is_empty(): - push_error("[UpdateApplier] No backups found") - return false - - backups.sort() - var latest_backup = backups.back() - - # Restore the backup - if _copy_file(backup_dir + latest_backup, game_dir + pck_name): - emit_signal("rollback_completed") - print("[UpdateApplier] Rolled back to: ", latest_backup) - return true - - return false diff --git a/launcher/scripts/update_applier.gd.uid b/launcher/scripts/update_applier.gd.uid deleted file mode 100644 index 920f6ff..0000000 --- a/launcher/scripts/update_applier.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://n2ptlbblexo6 diff --git a/launcher/scripts/version_checker.gd b/launcher/scripts/version_checker.gd deleted file mode 100644 index fae1b4c..0000000 --- a/launcher/scripts/version_checker.gd +++ /dev/null @@ -1,142 +0,0 @@ -extends Node -class_name VersionChecker - -const Config = preload("config.gd") -## Handles checking for game updates from the version manifest - -signal version_check_started -signal version_check_completed(has_update: bool, latest_version: String, changelog: Array) -signal version_check_failed(error: String) - -var http_request: HTTPRequest -var current_version: String = "0.0.0" -var latest_version: String = "0.0.0" -var manifest_data: Dictionary = {} - -func _ready() -> void: - http_request = HTTPRequest.new() - add_child(http_request) - http_request.request_completed.connect(_on_request_completed) - - # Load current local version - _load_local_version() - -func _load_local_version() -> void: - if FileAccess.file_exists(Config.LOCAL_VERSION_FILE): - var file := FileAccess.open(Config.LOCAL_VERSION_FILE, FileAccess.READ) - if file: - current_version = file.get_as_text().strip_edges() - file.close() - print("[VersionChecker] Local version: ", current_version) - else: - current_version = "0.0.0" - print("[VersionChecker] No local version found, assuming fresh install") - -func check_for_updates() -> void: - emit_signal("version_check_started") - print("[VersionChecker] Checking for updates at: ", Config.VERSION_MANIFEST_URL) - - var error := http_request.request(Config.VERSION_MANIFEST_URL) - if error != OK: - emit_signal("version_check_failed", "Failed to initiate version check request") - -func _on_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void: - if result != HTTPRequest.RESULT_SUCCESS: - emit_signal("version_check_failed", "Network error: " + str(result)) - return - - if response_code != 200: - emit_signal("version_check_failed", "Server returned error: " + str(response_code)) - return - - var json := JSON.new() - var parse_result := json.parse(body.get_string_from_utf8()) - if parse_result != OK: - emit_signal("version_check_failed", "Failed to parse version manifest") - return - - manifest_data = json.data - latest_version = manifest_data.get("latest_version", "0.0.0") - - var has_update := _compare_versions(current_version, latest_version) < 0 - var changelog: Array = _get_changelog_since(current_version) - - print("[VersionChecker] Latest version: ", latest_version, " | Has update: ", has_update) - emit_signal("version_check_completed", has_update, latest_version, changelog) - -func _compare_versions(v1: String, v2: String) -> int: - ## Returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2 - var parts1 := v1.split(".") - var parts2 := v2.split(".") - - for i in range(max(parts1.size(), parts2.size())): - var p1 := int(parts1[i]) if i < parts1.size() else 0 - var p2 := int(parts2[i]) if i < parts2.size() else 0 - - if p1 < p2: - return -1 - elif p1 > p2: - return 1 - - return 0 - -func _get_changelog_since(since_version: String) -> Array: - ## Get all changelog entries since the given version - var changelog: Array = [] - var releases: Array = manifest_data.get("releases", []) - - for release in releases: - var release_version: String = release.get("version", "") - if _compare_versions(since_version, release_version) < 0: - changelog.append({ - "version": release_version, - "date": release.get("date", ""), - "changes": release.get("changelog", []) - }) - - return changelog - -func get_download_info() -> Dictionary: - ## Returns info needed to download the latest version (platform-specific) - var platform := Config.get_platform_name() # "windows", "linux", or "macos" - var releases: Array = manifest_data.get("releases", []) - - for release in releases: - if release.get("version") == latest_version: - # Try platform-specific URL first, fall back to generic pck_url - var pck_url: String = "" - var pck_size: int = 0 - var checksum_md5: String = "" - var checksum_sha256: String = "" - - # Check for platform-specific fields - var platform_key := "pck_" + platform # e.g., "pck_windows", "pck_linux", "pck_macos" - if release.has(platform_key): - var platform_data: Dictionary = release.get(platform_key, {}) - pck_url = platform_data.get("url", "") - pck_size = platform_data.get("size", 0) - checksum_md5 = platform_data.get("checksum_md5", "") - checksum_sha256 = platform_data.get("checksum_sha256", "") - else: - # Fall back to generic fields (single PCK for all platforms) - pck_url = release.get("pck_url", "") - pck_size = release.get("pck_size", 0) - checksum_md5 = release.get("checksum_md5", "") - checksum_sha256 = release.get("checksum_sha256", "") - - return { - "url": pck_url, - "size": pck_size, - "checksum_md5": checksum_md5, - "checksum_sha256": checksum_sha256 - } - return {} - -func save_version(version: String) -> void: - ## Save the current version to the local version file - var file := FileAccess.open(Config.LOCAL_VERSION_FILE, FileAccess.WRITE) - if file: - file.store_string(version) - file.close() - current_version = version - print("[VersionChecker] Saved version: ", version) diff --git a/launcher/scripts/version_checker.gd.uid b/launcher/scripts/version_checker.gd.uid deleted file mode 100644 index d063d27..0000000 --- a/launcher/scripts/version_checker.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ptdurqksg1sv diff --git a/launcher/server/version.json b/launcher/server/version.json deleted file mode 100644 index 96940e9..0000000 --- a/launcher/server/version.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "latest_version": "0.9.0", - "minimum_launcher_version": "0.9.0", - "minimum_app_version": "0.9.0", - "releases": [ - { - "version": "0.9.0", - "date": "2026-03-09", - "pck_windows": { - "url": "https://your-host.com/releases/tekton-local-0.9.0-windows.pck", - "size": 52428800, - "checksum_md5": "", - "checksum_sha256": "" - }, - "changelog": [ - "Power-up spawning before countdown in Stop n Go", - "Fixed safe zone visuals persistence", - "Unified Game Over freeze for bots and players", - "Added wall overlap protection for spawned tiles", - "Restricted bot tile collection to current standing tile", - "Improved door visibility in Tekton Doors mode", - "Fixed knock logic when carrying a Tekton" - ], - "required": true - }, - { - "version": "1.0.0", - "date": "2025-12-10", - "pck_windows": { - "url": "https://your-host.com/releases/tekton-local-1.0.0-windows.pck", - "size": 52428800, - "checksum_md5": "", - "checksum_sha256": "" - }, - "pck_linux": { - "url": "https://your-host.com/releases/tekton-local-1.0.0-linux.pck", - "size": 52428800, - "checksum_md5": "", - "checksum_sha256": "" - }, - "pck_macos": { - "url": "https://your-host.com/releases/tekton-local-1.0.0-macos.pck", - "size": 52428800, - "checksum_md5": "", - "checksum_sha256": "" - }, - "pck_android": { - "url": "https://your-host.com/releases/tekton-local-1.0.0-android.pck", - "size": 52428800, - "checksum_md5": "", - "checksum_sha256": "" - }, - "pck_ios": { - "url": "https://your-host.com/releases/tekton-local-1.0.0-ios.pck", - "size": 52428800, - "checksum_md5": "", - "checksum_sha256": "" - }, - "changelog": [ - "Initial release", - "Multiplayer support via Nakama", - "Turn-based gameplay system", - "Lobby and matchmaking", - "Cross-platform support (Windows, Linux, macOS, Android, iOS)" - ], - "required": true - } - ], - "news": [ - { - "title": "Welcome to Tekton!", - "date": "2025-12-10", - "content": "The game is now available on all platforms! Join us for multiplayer tactical battles.", - "type": "announcement" - }, - { - "title": "Mobile Launch", - "date": "2025-12-10", - "content": "Tekton is now available on Android and iOS! Download from your app store.", - "type": "devlog" - } - ] -} \ No newline at end of file diff --git a/project.godot b/project.godot index 78d65be..77b8be8 100644 --- a/project.godot +++ b/project.godot @@ -15,8 +15,8 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true [application] config/name="Tekton Dash Armageddon" -config/version="2.1.2" -run/main_scene="res://scenes/ui/login_screen.tscn" +config/version="2.1.5" +run/main_scene="res://scenes/ui/boot_screen.tscn" config/features=PackedStringArray("4.6", "Forward Plus") config/icon="res://icon.svg" diff --git a/scenes/boot_screen.tscn b/scenes/boot_screen.tscn deleted file mode 100644 index 86e0119..0000000 --- a/scenes/boot_screen.tscn +++ /dev/null @@ -1,131 +0,0 @@ -[gd_scene load_steps=5 format=3 uid="uid://cyfjwldknv8m6"] - -[ext_resource type="Script" uid="uid://vgyrq5y5p7jw" path="res://scripts/ui/boot_screen.gd" id="1"] -[ext_resource type="Theme" uid="uid://da337sh5qxi0s" path="res://assets/themes/ui_theme.tres" id="2"] -[ext_resource type="Texture2D" uid="uid://40tlo0mda3wr" path="res://assets/graphics/main_menu/result_bg.png" id="3_v46t4"] -[ext_resource type="Texture2D" uid="uid://dvp0as6yyudco" path="res://assets/graphics/main_menu/bg_illust.png" id="4_okh44"] - -[node name="BootScreen" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -theme = ExtResource("2") -script = ExtResource("1") - -[node name="Background" type="TextureRect" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -texture = ExtResource("3_v46t4") -expand_mode = 2 - -[node name="Background2" type="TextureRect" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -texture = ExtResource("4_okh44") -expand_mode = 3 - -[node name="CenterContainer" type="CenterContainer" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] -layout_mode = 2 -theme_override_constants/separation = 24 - -[node name="Logo" type="Label" parent="CenterContainer/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1) -theme_override_font_sizes/font_size = 52 -text = "TEKTON" -horizontal_alignment = 1 - -[node name="Subtitle" type="Label" parent="CenterContainer/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1) -theme_override_font_sizes/font_size = 14 -text = "ARMAGEDDON" -horizontal_alignment = 1 - -[node name="StatusLabel" type="Label" parent="CenterContainer/VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_font_sizes/font_size = 16 -text = "Checking for updates..." -horizontal_alignment = 1 - -[node name="ProgressContainer" type="VBoxContainer" parent="CenterContainer/VBoxContainer"] -unique_name_in_owner = true -visible = false -layout_mode = 2 -theme_override_constants/separation = 8 - -[node name="ProgressBar" type="ProgressBar" parent="CenterContainer/VBoxContainer/ProgressContainer"] -unique_name_in_owner = true -custom_minimum_size = Vector2(320, 24) -layout_mode = 2 -show_percentage = false - -[node name="ProgressLabel" type="Label" parent="CenterContainer/VBoxContainer/ProgressContainer"] -unique_name_in_owner = true -layout_mode = 2 -text = "0%" -horizontal_alignment = 1 - -[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] -unique_name_in_owner = true -visible = false -layout_mode = 2 -theme_override_constants/separation = 16 -alignment = 1 - -[node name="UpdateButton" type="Button" parent="CenterContainer/VBoxContainer/ButtonContainer"] -unique_name_in_owner = true -custom_minimum_size = Vector2(160, 48) -layout_mode = 2 -text = "Update Now" - -[node name="SkipButton" type="Button" parent="CenterContainer/VBoxContainer/ButtonContainer"] -unique_name_in_owner = true -custom_minimum_size = Vector2(160, 48) -layout_mode = 2 -text = "Play Anyway" - -[node name="StoreButton" type="Button" parent="CenterContainer/VBoxContainer/ButtonContainer"] -unique_name_in_owner = true -visible = false -custom_minimum_size = Vector2(160, 48) -layout_mode = 2 -text = "Open Store" - -[node name="VersionLabel" type="Label" parent="."] -layout_mode = 1 -anchors_preset = 3 -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -120.0 -offset_top = -40.0 -offset_right = -16.0 -offset_bottom = -16.0 -grow_horizontal = 0 -grow_vertical = 0 -theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 0.6) -theme_override_font_sizes/font_size = 12 -text = "v0.9.0" -horizontal_alignment = 2 diff --git a/scenes/ui/boot_screen.tscn b/scenes/ui/boot_screen.tscn new file mode 100644 index 0000000..28471da --- /dev/null +++ b/scenes/ui/boot_screen.tscn @@ -0,0 +1,120 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/ui/boot_screen.gd" id="1_boot"] + +[node name="BootScreen" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_boot") + +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.0823529, 0.0823529, 0.109804, 1) + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MainVBox" type="VBoxContainer" parent="CenterContainer"] +custom_minimum_size = Vector2(600, 0) +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="Title" type="Label" parent="CenterContainer/MainVBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Tekton Dash Booting..." +horizontal_alignment = 1 + +[node name="ChangelogPanel" type="PanelContainer" parent="CenterContainer/MainVBox"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 300) +layout_mode = 2 + +[node name="Margin" type="MarginContainer" parent="CenterContainer/MainVBox/ChangelogPanel"] +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 + +[node name="VBox" type="VBoxContainer" parent="CenterContainer/MainVBox/ChangelogPanel/Margin"] +layout_mode = 2 + +[node name="ChangelogRichText" type="RichTextLabel" parent="CenterContainer/MainVBox/ChangelogPanel/Margin/VBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +bbcode_enabled = true +text = "Loading changelog..." + +[node name="HBox" type="HBoxContainer" parent="CenterContainer/MainVBox/ChangelogPanel/Margin/VBox"] +layout_mode = 2 +alignment = 1 + +[node name="PrevButton" type="Button" parent="CenterContainer/MainVBox/ChangelogPanel/Margin/VBox/HBox"] +unique_name_in_owner = true +layout_mode = 2 +text = " < Previous " + +[node name="PageLabel" type="Label" parent="CenterContainer/MainVBox/ChangelogPanel/Margin/VBox/HBox"] +unique_name_in_owner = true +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +text = "1 / 1" +horizontal_alignment = 1 + +[node name="NextButton" type="Button" parent="CenterContainer/MainVBox/ChangelogPanel/Margin/VBox/HBox"] +unique_name_in_owner = true +layout_mode = 2 +text = " Next > " + +[node name="StatusLabel" type="Label" parent="CenterContainer/MainVBox"] +unique_name_in_owner = true +layout_mode = 2 +text = "Checking for updates..." +horizontal_alignment = 1 + +[node name="ProgressBar" type="ProgressBar" parent="CenterContainer/MainVBox"] +unique_name_in_owner = true +layout_mode = 2 +show_percentage = false + +[node name="ProgressLabel" type="Label" parent="CenterContainer/MainVBox/ProgressBar"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +horizontal_alignment = 1 + +[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/MainVBox"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 20 +alignment = 1 + +[node name="SkipButton" type="Button" parent="CenterContainer/MainVBox/ButtonContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Skip" + +[node name="UpdateButton" type="Button" parent="CenterContainer/MainVBox/ButtonContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Download Update" diff --git a/scripts/managers/game_update_manager.gd b/scripts/managers/game_update_manager.gd index dbd7777..bbe60e4 100644 --- a/scripts/managers/game_update_manager.gd +++ b/scripts/managers/game_update_manager.gd @@ -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 diff --git a/scripts/ui/boot_screen.gd b/scripts/ui/boot_screen.gd index 7df0718..dfb1339 100644 --- a/scripts/ui/boot_screen.gd +++ b/scripts/ui/boot_screen.gd @@ -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() diff --git a/tiles_armagedon_a1.res b/tiles_armagedon_a1.res deleted file mode 100644 index db713e88672f701c858f670eb2d521335e777f4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11552 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 diff --git a/tools/build_patch.gd b/tools/build_patch.gd new file mode 100644 index 0000000..a6a0ab8 --- /dev/null +++ b/tools/build_patch.gd @@ -0,0 +1,54 @@ +#!/usr/bin/env -S godot --headless -s +extends SceneTree + +func _init(): + print("--- Starting Automated Patch Build ---") + + var output_file = "patch.pck" + var changed_files_txt = "changed_files.txt" + + if not FileAccess.file_exists(changed_files_txt): + print("ERROR: missing changed_files.txt. Cannot build patch.") + quit(1) + return + + var pck_packer = PCKPacker.new() + var err = pck_packer.pck_start(output_file) + if err != OK: + print("ERROR: Could not start PCK file: ", output_file) + quit(1) + return + + var file = FileAccess.open(changed_files_txt, FileAccess.READ) + var count = 0 + + while not file.eof_reached(): + var line = file.get_line().strip_edges() + if line.is_empty(): continue + + var res_path = "res://" + line + # Include automatically compiled scripts for GDScript + if line.ends_with(".gd"): + var remap_path = res_path.replace(".gd", ".gdc") + if FileAccess.file_exists(remap_path): + pck_packer.add_file(res_path, remap_path) + else: + pck_packer.add_file(res_path, res_path) + count += 1 + print("Adding (Script): ", res_path) + elif FileAccess.file_exists(res_path): + print("Adding to patch: ", res_path) + pck_packer.add_file(res_path, res_path) + count += 1 + else: + print("Warning: Changed file not found or Is Directory, skipping: ", res_path) + + # Always package our version/changelog list so clients see the new changelog + var version_manifest = "res://assets/data/version.json" + pck_packer.add_file(version_manifest, version_manifest) + print("Adding Version Manifest: ", version_manifest) + + pck_packer.flush(true) + print("--- Patch Build Complete! Packed %d files into %s ---" % [count + 1, output_file]) + + quit(0) diff --git a/tools/build_patch.gd.uid b/tools/build_patch.gd.uid new file mode 100644 index 0000000..8eecb34 --- /dev/null +++ b/tools/build_patch.gd.uid @@ -0,0 +1 @@ +uid://baco51hmps6s1 diff --git a/tools/create_patch.ps1 b/tools/create_patch.ps1 deleted file mode 100644 index 06c8c39..0000000 --- a/tools/create_patch.ps1 +++ /dev/null @@ -1,77 +0,0 @@ -# PowerShell script to create game patches for itch.io distribution -# Run this from the tekton-enet project root - -param( - [Parameter(Mandatory=$true)] - [string]$Version, - - [string]$GodotPath = "godot", - [string]$OutputDir = ".\dist" -) - -Write-Host "=== Tekton Patch Creator ===" -ForegroundColor Cyan -Write-Host "Creating patch for version: $Version" -ForegroundColor Yellow - -# Create output directory -if (-not (Test-Path $OutputDir)) { - New-Item -ItemType Directory -Path $OutputDir | Out-Null -} - -$PckName = "tekton-local-$Version.pck" -$PckPath = Join-Path $OutputDir $PckName - -# Update version file -Write-Host "Updating version.txt..." -ForegroundColor Green -Set-Content -Path ".\version.txt" -Value $Version - -# Export PCK only (no executable) -Write-Host "Exporting PCK file..." -ForegroundColor Green -Write-Host "Note: Run this export from the Godot editor or configure export preset first" - -# Generate checksums -Write-Host "Generating checksums..." -ForegroundColor Green - -if (Test-Path $PckPath) { - $md5 = (Get-FileHash -Path $PckPath -Algorithm MD5).Hash.ToLower() - $sha256 = (Get-FileHash -Path $PckPath -Algorithm SHA256).Hash.ToLower() - $size = (Get-Item $PckPath).Length - - Write-Host "MD5: $md5" -ForegroundColor White - Write-Host "SHA256: $sha256" -ForegroundColor White - Write-Host "Size: $size bytes" -ForegroundColor White - - # Generate JSON snippet for version.json - $jsonSnippet = @" -{ - "version": "$Version", - "date": "$(Get-Date -Format 'yyyy-MM-dd')", - "pck_url": "https://your-username.itch.io/tekton-local/files/$PckName", - "pck_size": $size, - "checksum_md5": "$md5", - "checksum_sha256": "$sha256", - "changelog": [ - "Add your changelog items here" - ], - "required": false -} -"@ - - $snippetPath = Join-Path $OutputDir "release-$Version.json" - Set-Content -Path $snippetPath -Value $jsonSnippet - - Write-Host "`nRelease JSON snippet saved to: $snippetPath" -ForegroundColor Green - Write-Host "Copy this into your version.json releases array" -ForegroundColor Yellow -} else { - Write-Host "PCK file not found at: $PckPath" -ForegroundColor Red - Write-Host "Please export the PCK manually from Godot:" -ForegroundColor Yellow - Write-Host " 1. Open project in Godot Editor" -ForegroundColor White - Write-Host " 2. Go to Project > Export..." -ForegroundColor White - Write-Host " 3. Configure Windows Desktop preset" -ForegroundColor White - Write-Host " 4. Click 'Export PCK/ZIP' and save as $PckName" -ForegroundColor White -} - -Write-Host "`n=== Patch Creation Complete ===" -ForegroundColor Cyan -Write-Host "Next steps:" -ForegroundColor Yellow -Write-Host " 1. Upload $PckName to itch.io" -ForegroundColor White -Write-Host " 2. Update version.json with the new release entry" -ForegroundColor White -Write-Host " 3. Upload updated version.json to itch.io" -ForegroundColor White diff --git a/version.txt b/version.txt deleted file mode 100644 index ac39a10..0000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.9.0