102 Commits

Author SHA1 Message Date
god bd8b81bbc1 ci: install zip for binary packaging
Release / Build & Release (push) Successful in 7m32s
2026-07-04 23:18:26 +08:00
god a2d008a480 ci: include changelog in release body
Release / Build & Release (push) Successful in 18m31s
2026-07-04 22:59:49 +08:00
god 260a448a70 docs: changelog for v2.4.3
Release / Build & Release (push) Failing after 12m16s
2026-07-04 22:51:47 +08:00
god e73421c9b1 chore: remove stale test files
Release / Build & Release (push) Failing after 18m54s
2026-07-04 22:15:32 +08:00
god 2dd3020d0b feat: use Gitea raw endpoint instead of gitea-pages for patches 2026-07-04 22:15:24 +08:00
god fbca048aba ci: no templates needed for pck export, cache godot binary
Release / Build & Release (push) Successful in 16m34s
2026-07-04 22:07:07 +08:00
god 4c6c8e1587 ci: fix apt-get update before install, guard template extraction 2026-07-04 21:56:03 +08:00
god 939d2b0ac2 ci: shallow checkout for patch deploy 2026-07-04 21:47:58 +08:00
god e845c1be25 ci: use cached godot export templates for patch deployment 2026-07-04 20:21:15 +08:00
god 1f6bdf4d06 chore: bump version to 2.4.3 for patch release 2026-07-04 20:08:26 +08:00
god 36ae0d479d docs: mark multiplayer gauntlet bug as resolved 2026-07-04 20:01:55 +08:00
god c23c5f77bc fix: correct indentation of has_method check in GauntletManager to prevent multiplayer client crash 2026-07-04 20:01:39 +08:00
god 1691d8ebc9 docs: mark currency sync bug as resolved 2026-07-04 19:56:30 +08:00
god ab93f047ab fix: keep gacha panel wallet UI synced with global wallet updates 2026-07-04 19:56:19 +08:00
god b15e1153e0 fix: multiple punch SFX spam
Fixes #74

The multiple punch SFX bug has been fixed.
Cause: The is_charged_strike state was never cleared after pushing a player, causing the push logic to re-trigger and play the sound every frame while the movement key was held.
Fix: is_charged_strike is now immediately cleared upon a successful push.
2026-07-04 19:33:35 +08:00
god e6e4c7b6f6 docs: mark playerboard desync and punch SFX bugs as resolved 2026-07-04 19:31:37 +08:00
god d524f23104 fix: prevent multiple attack/smash SFX spam by clearing charged strike state immediately 2026-07-04 19:31:26 +08:00
god 7762a82d18 fix: wait for playerboard before applying multiplayer sync 2026-07-04 19:17:27 +08:00
god 0786638a98 docs: update clone instructions for Gitea SSH and HTTPS 2026-07-04 11:38:58 +08:00
god 08a527195f patch system: use HTTPS for raw.klud.top 2026-07-04 10:48:21 +08:00
god 4a3637482c patch system: use raw.klud.top, remove GitHub raw URLs 2026-07-04 10:11:45 +08:00
god 8414f48106 patch system: gitea-pages serving, MANIFEST_URL updated 2026-07-04 10:00:57 +08:00
god 7c6f66e821 chore: upgrade Godot 4.6 -> 4.7 stable in CI and local 2026-07-04 09:20:08 +08:00
god 2643ef24fa chore: test god commit 2026-07-04 09:11:47 +08:00
adtpdn c47fadf152 ci: use god account for git checkout, update release token 2026-07-04 09:08:59 +08:00
adtpdn 4486e4d3fd fix: set macOS bundle identifier for export
Release / Build & Release (push) Successful in 6m40s
2026-07-04 08:52:19 +08:00
adtpdn 6971c27d77 ci: use GITEA_TOKEN env var, skip macOS upload if missing
Release / Build & Release (push) Successful in 24m57s
2026-07-04 01:59:48 +08:00
adtpdn 7e6e0f7bef ci: guard macOS mv if export fails
Release / Build & Release (push) Failing after 5m45s
2026-07-04 01:48:36 +08:00
adtpdn 869f670605 ci: macOS export can fail, allow continue
Release / Build & Release (push) Failing after 5m12s
2026-07-04 01:42:33 +08:00
adtpdn db061a7946 ci: fix second ref to .official dir
Release / Build & Release (push) Failing after 5m14s
2026-07-04 01:34:28 +08:00
adtpdn 20cd2d08b8 ci: fix export templates path 4.6.stable not .official
Release / Build & Release (push) Failing after 2m52s
2026-07-04 01:28:24 +08:00
adtpdn 1652630153 ci: update URLs from local to VPS
Release / Build & Release (push) Failing after 4m3s
2026-07-04 01:13:17 +08:00
adtpdn 34039db92c chore(ci): use HTTP manual clone with Gitea internal Docker network IP, ditch ssh
Release / Build & Release (push) Failing after 2m20s
2026-07-03 18:26:11 +08:00
adtpdn bf9ae51702 chore(ci): use explicit ip for gitea in ssh config
Release / Build & Release (push) Failing after 2m26s
2026-07-03 18:21:13 +08:00
adtpdn 7a02eee277 chore(ci): use manual clone because checkout action overrides ssh settings
Release / Build & Release (push) Failing after 33s
2026-07-03 18:17:21 +08:00
adtpdn d6daed62b8 chore(ci): use proper gitea act_runner network config to resolve gitea internally
Release / Build & Release (push) Failing after 1m14s
2026-07-03 18:14:05 +08:00
adtpdn 0548f54168 chore(ci): rewrite thunderobot without ts suffix down to port 22 as well
Release / Build & Release (push) Has been cancelled
2026-07-03 18:04:37 +08:00
adtpdn 3fe8de2e32 chore(ci): alias magicdns thunderobot.tail5d6e8e.ts.net and strip port 222
Release / Build & Release (push) Failing after 14m23s
2026-07-03 18:04:13 +08:00
adtpdn f40dae5a03 chore(ci): remap port 222 to port 22 via git config
Release / Build & Release (push) Failing after 7m30s
2026-07-03 18:02:08 +08:00
adtpdn ab3ffbbec8 chore(ci): alias both gitea and thunderobot directly to IP
Release / Build & Release (push) Failing after 7m27s
2026-07-03 18:01:36 +08:00
adtpdn da5c319a5b chore(ci): use internal gitea docker networking alias for checkout
Release / Build & Release (push) Failing after 1m5s
2026-07-03 17:56:10 +08:00
adtpdn 5e5d0c8ecf chore(ci): move network connect step before checkout
Release / Build & Release (push) Failing after 1m3s
2026-07-03 17:41:56 +08:00
adtpdn b5e22f3ca5 chore(ci): export to exe/x86_64 then zip, ignore gdscript warning
Release / Build & Release (push) Failing after 7s
2026-07-03 17:21:25 +08:00
adtpdn 8abf07a0d4 chore(ci): route SSH clone via docker bridge gateway instead of tailscale IP
Release / Build & Release (push) Failing after 5m1s
2026-07-03 17:13:30 +08:00
adtpdn 350ae269f2 chore(ci): bypass alias entirely and use explicit IP for clone
Release / Build & Release (push) Failing after 4m8s
2026-07-03 17:12:31 +08:00
adtpdn e8604e2c02 chore(ci): use correct tailscale IP and ssh format
Release / Build & Release (push) Failing after 4m52s
2026-07-03 17:10:39 +08:00
adtpdn c9995f8578 chore(ci): use manual git clone via ssh instead of checkout action
Release / Build & Release (push) Failing after 3s
2026-07-03 17:07:16 +08:00
adtpdn 4aa765c502 chore(ci): use printf for ssh config, drop ssh-keyscan
Release / Build & Release (push) Failing after 40s
2026-07-03 17:05:33 +08:00
adtpdn 1d653bb7d0 chore(ci): use ssh config alias for thunderobot host
Release / Build & Release (push) Failing after 9s
2026-07-03 17:02:28 +08:00
adtpdn 4fe0378d1c chore(ci): fix ssh-keyscan using direct IP
Release / Build & Release (push) Failing after 3s
2026-07-03 17:01:16 +08:00
adtpdn fb58e62fd9 chore(ci): fix ssh-keyscan host resolution
Release / Build & Release (push) Failing after 8s
2026-07-03 17:00:48 +08:00
adtpdn aa45bb0afd chore(ci): use cached templates, build to zip, export windows/linux/macos with prefix
Release / Build & Release (push) Failing after 28s
2026-07-03 16:50:02 +08:00
adtpdn d05ebdff05 chore(ci): docker network connect gitea_default for API access
Release / Build & Release (push) Failing after 54s
2026-07-03 16:00:35 +08:00
adtpdn e984c1f8b5 chore(ci): use git.klud.top API (internet-reachable)
Release / Build & Release (push) Failing after 13m49s
2026-07-03 15:44:30 +08:00
adtpdn 99d38134b8 chore(ci): host network, localhost API
Release / Build & Release (push) Failing after 4m10s
2026-07-03 15:37:43 +08:00
adtpdn 66bc1658a4 chore(ci): use Tailscale IP for API, not gitea hostname
Release / Build & Release (push) Failing after 4m11s
2026-07-03 15:21:56 +08:00
adtpdn 3cb8a606b5 chore(ci): use gitea:3000 internal API, fix upload URLs
Release / Build & Release (push) Failing after 4m17s
2026-07-03 15:07:22 +08:00
adtpdn 19e7f619ab feat(gauntlet): replace Cleanser with Ghost powerup sticky bypass (v2.4.2)
- Remove entire Cleanser system (signal, vars, HUD, input, RPCs, bot AI)
- Ghost (Invisible Mode) now bypasses sticky tiles in Gauntlet
- Grant Ghost powerup every 2 missions instead of Cleanser charges
- Ghost tiles spawn naturally on Gauntlet arena (15% chance)
- Bots use Ghost powerup when boxed in by sticky tiles
- Players pushed into sticky while Ghost are not slowed
- Remove use_cleanser input action from project.godot
- Remove CleanserHBox UI from gauntlet_hud.tscn
- Bump version to 2.4.2
2026-07-03 14:55:03 +08:00
adtpdn 5ba7de3fd6 chore(ci): single job, no upload-artifact (not supported)
Release / Build & Release (push) Failing after 4m14s
2026-07-03 14:51:57 +08:00
adtpdn a89e54783f chore(ci): cp from /cache with wget fallback, fix release tokens
Release / Export Linux (push) Failing after 7m20s
Release / Export Windows (push) Failing after 7m25s
Release / Create Release (push) Has been skipped
2026-07-03 14:38:26 +08:00
adtpdn 376be28366 fix(ci): switch autoload refs from uid:// to res:// paths
Nakama, Satori, and SettingsManager used uid:// references in
project.godot which fail in CI headless export (UID cache not
built). Switch to res:// file paths to fix infinite reimport loop.
2026-07-03 13:52:04 +08:00
adtpdn 0415875128 chore: remove unused workflows
Release / Export Windows (push) Failing after 40s
Release / Export Linux (push) Failing after 42s
Release / Create Release (push) Has been skipped
2026-07-03 13:31:19 +08:00
adtpdn 03028413ca chore(ci): only tag release + patch, no testing, cp from /cache
Test Suite / Unit Tests (GUT) (push) Successful in 46s
Release / Export Windows (push) Failing after 45s
Release / Export Linux (push) Failing after 16s
Release / Create Release (push) Has been skipped
Upload PCK to Gitea Release / upload (push) Failing after 47s
Test Suite / Security Scan (push) Failing after 13m23s
Test Suite / Code Style Check (push) Failing after 13m41s
Test Suite / Integration Tests (push) Failing after 14m26s
Build and Upload Binaries / build (push) Failing after 17m44s
2026-07-03 13:25:27 +08:00
adtpdn 340ba13a48 chore(ci): prefer shared host cache /home/dev/godot-cache, fallback to download
CI / Export Linux (push) Failing after 8m9s
CI / Export Windows (push) Failing after 8m11s
Test Suite / Unit Tests (GUT) (push) Successful in 59s
Test Suite / Code Style Check (push) Failing after 39s
Test Suite / Integration Tests (push) Failing after 4m18s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 5m35s
2026-07-03 12:34:08 +08:00
adtpdn 04f7fdd2dd chore(ci): use LAN mirror 192.168.0.114:9009 instead of docker bridge 172.17.0.1
CI / Create Release (push) Has been cancelled
CI / Export Windows (push) Has been cancelled
CI / Export Linux (push) Has been cancelled
Test Suite / Code Style Check (push) Has been cancelled
Test Suite / Security Scan (push) Has been cancelled
Test Suite / Unit Tests (GUT) (push) Has been cancelled
Test Suite / Integration Tests (push) Has been cancelled
2026-07-03 12:19:45 +08:00
adtpdn edce1fa1fa chore(ci): use local godot mirror on 172.17.0.1:9009
CI / Create Release (push) Has been cancelled
CI / Export Windows (push) Has been cancelled
CI / Export Linux (push) Has been cancelled
Test Suite / Integration Tests (push) Has been cancelled
Test Suite / Code Style Check (push) Has been cancelled
Test Suite / Security Scan (push) Has been cancelled
Test Suite / Unit Tests (GUT) (push) Has been cancelled
2026-07-03 12:03:35 +08:00
adtpdn 6ac9acea35 chore(ci): fix templates path nesting and replace failing android setup action with sdkmanager
CI / Export Linux (push) Failing after 6m37s
CI / Export Windows (push) Failing after 6m45s
Test Suite / Unit Tests (GUT) (push) Successful in 44s
CI / Export Android (push) Failing after 5m11s
Test Suite / Code Style Check (push) Failing after 24s
Test Suite / Integration Tests (push) Failing after 5m34s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 5m40s
2026-07-03 11:18:03 +08:00
adtpdn 9ddb1dd199 chore(ci): fix export templates install and android cmdline version
CI / Export Windows (push) Failing after 5m23s
CI / Export Linux (push) Failing after 5m3s
Test Suite / Unit Tests (GUT) (push) Successful in 51s
CI / Export Android (push) Failing after 3m20s
Test Suite / Code Style Check (push) Failing after 34s
Test Suite / Integration Tests (push) Failing after 4m1s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 5m37s
2026-07-03 11:03:14 +08:00
adtpdn 74de7f15ef chore: trigger CI verify SSH secret
CI / Export Linux (push) Failing after 4s
CI / Export Android (push) Failing after 5m29s
Test Suite / Unit Tests (GUT) (push) Successful in 41s
CI / Export Windows (push) Failing after 6m47s
Test Suite / Code Style Check (push) Failing after 40s
Test Suite / Integration Tests (push) Failing after 4m5s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 5m33s
2026-07-03 10:52:12 +08:00
adtpdn d40a242cbc feat(gauntlet): shrink arena per phase 20x20 -> 18x18 -> 7x7; sticky cells block movement
CI / Export Linux (push) Failing after 4s
CI / Export Windows (push) Failing after 36s
CI / Export Android (push) Failing after 38s
Test Suite / Unit Tests (GUT) (push) Failing after 26s
Test Suite / Integration Tests (push) Failing after 29s
Test Suite / Code Style Check (push) Failing after 38s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 51s
2026-07-02 18:11:14 +08:00
GMaysa 419771c50b ci: add diagnostic step for git remote connectivity in CI workflow
CI / Export Linux (push) Failing after 4s
CI / Export Android (push) Failing after 31s
CI / Export Windows (push) Failing after 43s
Test Suite / Unit Tests (GUT) (push) Failing after 33s
Test Suite / Integration Tests (push) Failing after 30s
Test Suite / Security Scan (push) Failing after 36s
Test Suite / Code Style Check (push) Failing after 41s
CI / Create Release (push) Has been skipped
2026-07-02 18:10:48 +08:00
GMaysa e7a0717aff ci: update SSH configuration for Gitea checkout step to improve security and fetch full history
CI / Export Windows (push) Failing after 38s
CI / Export Linux (push) Failing after 38s
CI / Export Android (push) Failing after 39s
Test Suite / Unit Tests (GUT) (push) Failing after 40s
Test Suite / Code Style Check (push) Failing after 32s
Test Suite / Integration Tests (push) Failing after 36s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 37s
2026-07-02 18:07:34 +08:00
GMaysa 754fb0d0a3 ci: enable step debugging for Windows export job
CI / Export Linux (push) Failing after 33s
CI / Export Windows (push) Failing after 41s
Test Suite / Unit Tests (GUT) (push) Failing after 33s
CI / Export Android (push) Failing after 41s
Test Suite / Code Style Check (push) Failing after 27s
Test Suite / Integration Tests (push) Failing after 32s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 39s
2026-07-02 17:58:39 +08:00
GMaysa 2fd8130743 ci: fix formatting of checkout step in CI workflow
CI / Export Linux (push) Failing after 39s
CI / Export Windows (push) Failing after 47s
Test Suite / Unit Tests (GUT) (push) Failing after 33s
CI / Export Android (push) Failing after 42s
Test Suite / Integration Tests (push) Failing after 31s
Test Suite / Code Style Check (push) Failing after 34s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 39s
2026-07-02 17:55:24 +08:00
GMaysa 5ad9952750 ci: update checkout step to fetch full history in CI workflow
Test Suite / Integration Tests (push) Failing after 28s
Test Suite / Unit Tests (GUT) (push) Failing after 43s
Test Suite / Code Style Check (push) Failing after 27s
Test Suite / Security Scan (push) Failing after 36s
2026-07-02 17:53:27 +08:00
GMaysa 720882f0dc ci: add network connectivity tests for Gitea in deploy_patch.yml
CI / Export Linux (push) Failing after 38s
CI / Export Windows (push) Failing after 39s
Test Suite / Unit Tests (GUT) (push) Failing after 30s
CI / Export Android (push) Failing after 32s
Test Suite / Integration Tests (push) Failing after 32s
Test Suite / Code Style Check (push) Failing after 37s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 41s
2026-07-02 17:42:51 +08:00
GMaysa 456ca94786 ci: update SSH configuration in deploy_patch.yml to use user-specific config and improve security
CI / Export Linux (push) Failing after 30s
CI / Export Windows (push) Failing after 36s
Test Suite / Unit Tests (GUT) (push) Failing after 33s
CI / Export Android (push) Failing after 37s
Test Suite / Code Style Check (push) Failing after 37s
Test Suite / Integration Tests (push) Failing after 40s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 40s
2026-07-02 17:39:38 +08:00
GMaysa 78f8c5bf5e ci: update SSH config in deploy_patch.yml to use correct host for Gitea
CI / Export Linux (push) Failing after 32s
CI / Export Windows (push) Failing after 43s
CI / Export Android (push) Failing after 37s
Test Suite / Unit Tests (GUT) (push) Failing after 30s
Test Suite / Integration Tests (push) Failing after 29s
Test Suite / Code Style Check (push) Failing after 38s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 39s
2026-07-02 17:31:16 +08:00
GMaysa 58b66bcb8f ci: update SSH config setup to append instead of using sudo tee for Gitea
CI / Export Windows (push) Failing after 36s
CI / Export Linux (push) Failing after 36s
CI / Export Android (push) Failing after 30s
Test Suite / Unit Tests (GUT) (push) Failing after 32s
Test Suite / Integration Tests (push) Failing after 29s
Test Suite / Code Style Check (push) Failing after 29s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 35s
2026-07-02 17:23:04 +08:00
GMaysa 477c7ca7ec ci: update SSH configuration setup for Gitea to use system-wide ssh_config
CI / Export Windows (push) Failing after 4s
CI / Export Linux (push) Failing after 3s
CI / Export Android (push) Failing after 5s
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 2s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 13s
2026-07-02 17:19:58 +08:00
GMaysa ecb080e943 ci: add SSH configuration setup for Gitea in workflows
CI / Export Linux (push) Failing after 28s
CI / Export Windows (push) Failing after 38s
CI / Export Android (push) Failing after 36s
Test Suite / Unit Tests (GUT) (push) Failing after 32s
Test Suite / Integration Tests (push) Failing after 33s
Test Suite / Code Style Check (push) Failing after 32s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 13m25s
2026-07-02 16:53:31 +08:00
GMaysa e539a862d5 Clean the yml script
CI / Export Windows (push) Failing after 43s
CI / Export Linux (push) Failing after 1m4s
CI / Export Android (push) Failing after 55s
Test Suite / Unit Tests (GUT) (push) Failing after 39s
Test Suite / Integration Tests (push) Failing after 46s
Test Suite / Code Style Check (push) Failing after 46s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 1m1s
2026-07-02 16:40:52 +08:00
adtpdn 82763e4d5a ci: fix ssh clone url in build_binaries and upload_pck
CI / Run Tests (push) Failing after 35s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 30s
Test Suite / Integration Tests (push) Failing after 32s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 11m17s
Test Suite / Code Style Check (push) Failing after 11m50s
2026-07-02 16:12:46 +08:00
adtpdn c30888b74c ci: enforce specific IP and Port 222 for gitea ssh route
CI / Run Tests (push) Has been cancelled
CI / Export Windows (push) Has been cancelled
CI / Export Linux (push) Has been cancelled
CI / Export Android (push) Has been cancelled
CI / Create Release (push) Has been cancelled
Test Suite / Unit Tests (GUT) (push) Has been cancelled
Test Suite / Integration Tests (push) Has been cancelled
Test Suite / Code Style Check (push) Has been cancelled
Test Suite / Security Scan (push) Has been cancelled
2026-07-02 16:08:36 +08:00
adtpdn 858bf08212 ci: map internal gitea hostname to external ip before checkout
CI / Export Windows (push) Has been cancelled
CI / Export Linux (push) Has been cancelled
CI / Export Android (push) Has been cancelled
CI / Create Release (push) Has been cancelled
CI / Run Tests (push) Has been cancelled
Test Suite / Unit Tests (GUT) (push) Has been cancelled
Test Suite / Integration Tests (push) Has been cancelled
Test Suite / Code Style Check (push) Has been cancelled
Test Suite / Security Scan (push) Has been cancelled
2026-07-02 16:01:13 +08:00
adtpdn 64630662ac ci: replace missing godot action with manual wget download
CI / Run Tests (push) Failing after 45s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 47s
Test Suite / Integration Tests (push) Failing after 37s
Test Suite / Code Style Check (push) Failing after 40s
Test Suite / Security Scan (push) Failing after 54s
CI / Create Release (push) Has been skipped
2026-07-02 15:47:55 +08:00
adtpdn 187b530cbf ci: enforce ssh checkout on all gitea workflows
Adds ssh-key to actions/checkout configurations and fixes yaml indentation in upload_pck.yml
2026-07-02 15:40:37 +08:00
adtpdn 6da55003e2 Fix checkout to use SSH
CI / Run Tests (push) Failing after 3s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 3s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 3s
Test Suite / Security Scan (push) Failing after 1m51s
CI / Create Release (push) Has been skipped
2026-07-02 15:04:07 +08:00
adtpdn 598065f255 feat: test workflows
CI / Run Tests (push) Failing after 3s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 2s
Test Suite / Security Scan (push) Failing after 43s
CI / Create Release (push) Has been skipped
Upload PCK to Gitea Release / upload (push) Failing after 1s
2026-07-02 10:33:50 +08:00
adtpdn c471afaee4 Merge branch 'experimental' of ssh://100.79.174.108:222/danchie/tekton into experimental
CI / Run Tests (push) Failing after 2s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 2s
Test Suite / Security Scan (push) Failing after 58s
CI / Create Release (push) Has been skipped
2026-07-02 09:20:29 +08:00
adtpdn 0228fcfd92 update .gitignore 2026-07-02 09:15:45 +08:00
adtpdn 8fc3c1b915 Fix README breadcrumb/wiki links to absolute URLs
CI / Run Tests (push) Failing after 2s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 2s
Test Suite / Security Scan (push) Failing after 52s
CI / Create Release (push) Has been skipped
2026-07-02 01:58:18 +08:00
adtpdn 74c1e86c32 Use absolute wiki URLs in README
CI / Run Tests (push) Failing after 3s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 2s
Test Suite / Security Scan (push) Failing after 51s
CI / Create Release (push) Has been skipped
2026-07-02 01:56:46 +08:00
adtpdn 19f10a4486 Remove wiki folder from main repo; wiki pages live in tekton.wiki.git
CI / Run Tests (push) Failing after 3s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 2s
Test Suite / Security Scan (push) Failing after 53s
CI / Create Release (push) Has been skipped
2026-07-02 01:54:18 +08:00
adtpdn 8255b1f465 Fix README wiki links to relative paths
CI / Run Tests (push) Failing after 3s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 2s
Test Suite / Security Scan (push) Failing after 1m0s
CI / Create Release (push) Has been skipped
2026-07-02 01:50:54 +08:00
adtpdn 625ff5ec69 Replace SSH tutorial in README with wiki pages by OS
CI / Run Tests (push) Failing after 2s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 3s
Test Suite / Code Style Check (push) Failing after 2s
Test Suite / Security Scan (push) Failing after 1m1s
CI / Create Release (push) Has been skipped
Linux, macOS, Windows SSH setup guides moved to wiki. README becomes index with wiki links and breadcrumb to existing docs.
2026-07-02 01:35:51 +08:00
adtpdn ccd759da45 Add SSH clone tutorial for tailnet users via README
CI / Run Tests (push) Failing after 3s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 2s
Test Suite / Security Scan (push) Failing after 58s
CI / Create Release (push) Has been skipped
2026-07-02 01:18:54 +08:00
adtpdn a708da8836 feat: update CI/CD for .gitea
CI / Run Tests (push) Failing after 3s
CI / Export Windows (push) Has been skipped
CI / Export Linux (push) Has been skipped
CI / Export Android (push) Has been skipped
Test Suite / Unit Tests (GUT) (push) Failing after 2s
Test Suite / Integration Tests (push) Failing after 2s
Test Suite / Code Style Check (push) Failing after 3s
Test Suite / Security Scan (push) Failing after 12m56s
CI / Create Release (push) Has been skipped
2026-07-01 18:33:27 +08:00
adtpdn d2156c6d1a feat: edit CI/CD 2026-07-01 18:33:08 +08:00
adtpdn 5b34f8b96f docs: move developer docs to wiki; README is now a pointer 2026-07-01 15:47:02 +08:00
adtpdn b4ce7453c3 feat: cleanup mess 2026-07-01 15:07:00 +08:00
adtpdn 594a0ce84d Fix gauntlet_manager indentation and scope 2026-07-01 11:28:07 +08:00
69 changed files with 742 additions and 1305 deletions
+154
View File
@@ -0,0 +1,154 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
name: Build & Release
runs-on: ubuntu-latest
timeout-minutes: 30
env:
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
TAG_NAME: ${{ github.ref_name }}
steps:
- name: Install tools
run: apt-get update -qq && apt-get install -y -qq curl unzip zip
- name: Checkout Code
run: |
git config --global credential.helper store
echo "http://god:${{ secrets.TEKTON_RELEASE_TOKEN }}@52.74.133.55:3000" > ~/.git-credentials
git clone http://52.74.133.55:3000/danchie/tekton.git .
git checkout $TAG_NAME
- name: Setup Godot (Cached)
run: |
apt-get update -qq && apt-get install -y -qq unzip curl
if [ ! -f /cache/godot_4.7 ]; then
echo "Downloading Godot 4.7..."
curl -sL -o /tmp/godot.zip "https://github.com/godotengine/godot-builds/releases/download/4.7-stable/Godot_v4.7-stable_linux.x86_64.zip"
unzip -q -o /tmp/godot.zip -d /cache/
mv /cache/Godot_v4.7-stable_linux.x86_64 /cache/godot_4.7
fi
cp /cache/godot_4.7 /usr/local/bin/godot
chmod +x /usr/local/bin/godot
mkdir -p ~/.local/share/godot/export_templates/4.7.stable
if [ ! -f /cache/Godot_v4.7-stable_export_templates.tpz ]; then
echo "Downloading templates..."
curl -sL -o /cache/Godot_v4.7-stable_export_templates.tpz \
"https://github.com/godotengine/godot-builds/releases/download/4.7-stable/Godot_v4.7-stable_export_templates.tpz"
fi
cd ~/.local/share/godot/export_templates/4.7.stable
unzip -q -o /cache/Godot_v4.7-stable_export_templates.tpz
mv templates/* .
rm -rf templates
cd $GITHUB_WORKSPACE
mkdir -p build
- name: Export Windows
run: |
mkdir -p build/windows
cp addons/godotsteam/libgodotsteam* build/windows/ 2>/dev/null || true
godot --headless --export-release "Windows Desktop" build/windows/tekton_armageddon_windows.exe || true
cd build/windows && zip -r ../tekton_armageddon_windows_${TAG_NAME}.zip .
- name: Export Linux
run: |
mkdir -p build/linux
godot --headless --export-release "Linux/X11" build/linux/tekton_armageddon_linux.x86_64 || true
cd build/linux && zip -r ../tekton_armageddon_linux_${TAG_NAME}.zip .
- name: Export macOS
run: |
mkdir -p build/macos
godot --headless --export-release "macOS" build/macos/tekton_armageddon_macos.zip 2>&1 | tail -5 || true
if [ -f build/macos/tekton_armageddon_macos.zip ]; then
mv build/macos/tekton_armageddon_macos.zip build/tekton_armageddon_macos_${TAG_NAME}.zip
fi
- name: Extract changelog
run: |
# Extract changelog for this tag version from CHANGELOG_DRAFT.md
V="${TAG_NAME#v}"
BODY=$(awk -v ver="[$V]" '
/^## / { if (found) exit }
/^## / && index($0, ver) { found=1; next }
found { print }
' CHANGELOG_DRAFT.md | sed 's/^ *//')
echo "CHANGELOG_BODY<<EOF" >> $GITHUB_ENV
echo "$BODY" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Create Gitea Release
run: |
set -e
API="http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases"
TAG="$TAG_NAME"
echo "Checking existing release for $TAG..."
RELEASE_JSON=$(curl -s -H "Authorization: token $GITEA_TOKEN" "$API/tags/$TAG" 2>/dev/null || echo "")
RELEASE_ID=$(echo "$RELEASE_JSON" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*' || true)
if [ -z "$RELEASE_ID" ]; then
echo "Creating new release for $TAG..."
# Escape body for JSON
BODY_ESCAPED=$(echo "$CHANGELOG_BODY" | python3 -c "import json,sys; print(json.dumps(sys.stdin.read().strip()))" 2>/dev/null || echo '""')
RELEASE_JSON=$(curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":$BODY_ESCAPED,\"draft\":true}" \
"$API")
echo "API response: $RELEASE_JSON"
RELEASE_ID=$(echo "$RELEASE_JSON" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
if [ -z "$RELEASE_ID" ]; then
echo "FATAL: Could not create release"
exit 1
fi
fi
echo "release_id=$RELEASE_ID"
echo "$RELEASE_ID" > /tmp/release_id.txt
- name: Upload Windows asset
run: |
RELEASE_ID=$(cat /tmp/release_id.txt)
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: multipart/form-data" \
-F "attachment=@build/tekton_armageddon_windows_${TAG_NAME}.zip" \
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
echo "Windows uploaded"
- name: Upload Linux asset
run: |
RELEASE_ID=$(cat /tmp/release_id.txt)
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: multipart/form-data" \
-F "attachment=@build/tekton_armageddon_linux_${TAG_NAME}.zip" \
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
echo "Linux uploaded"
- name: Upload macOS asset
run: |
RELEASE_ID=$(cat /tmp/release_id.txt)
if [ -f "build/tekton_armageddon_macos_${TAG_NAME}.zip" ]; then
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: multipart/form-data" \
-F "attachment=@build/tekton_armageddon_macos_${TAG_NAME}.zip" \
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
echo "macOS uploaded"
else
echo "macOS asset not built, skipping"
fi
- name: Publish release
run: |
RELEASE_ID=$(cat /tmp/release_id.txt)
curl -s -X PATCH \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"draft":false}' \
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID"
echo "Published: https://git.klud.top/danchie/tekton/releases/tag/$TAG_NAME"
+68
View File
@@ -0,0 +1,68 @@
name: Deploy Patch
on:
workflow_dispatch:
inputs:
version:
description: 'Patch version (e.g., 2.4.2)'
required: true
type: string
notes:
description: 'Release notes'
required: false
type: string
jobs:
build-and-deploy:
name: Build & Deploy Patch
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository (shallow)
env:
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
run: |
git clone --depth 1 http://god:$GITEA_TOKEN@52.74.133.55:3000/danchie/tekton.git .
git config user.name "god"
git config user.email "god@noreply.git.klud.top"
- name: Setup Godot (Cached)
run: |
if [ ! -f /cache/godot_4.7 ]; then
echo "Downloading Godot 4.7..."
apt-get update -qq && apt-get install -y -qq unzip curl
curl -sL -o /tmp/godot.zip "https://github.com/godotengine/godot-builds/releases/download/4.7-stable/Godot_v4.7-stable_linux.x86_64.zip"
unzip -q -o /tmp/godot.zip -d /cache/
mv /cache/Godot_v4.7-stable_linux.x86_64 /cache/godot_4.7
fi
cp /cache/godot_4.7 /usr/local/bin/godot
chmod +x /usr/local/bin/godot
mkdir -p build
- name: Generate version.json & bump version
env:
PATCH_VERSION: ${{ github.event.inputs.version }}
PATCH_NOTES: ${{ github.event.inputs.notes }}
run: |
python3 tools/generate_version_json.py --skip-changelog
- name: Export patch PCK
run: |
godot --headless --export-pack "Windows Desktop" build/patch.pck 2>&1 | tail -5
- name: Push to patches branch
env:
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
run: |
mkdir -p patch-deploy
cp build/patch.pck patch-deploy/
cp assets/data/version.json patch-deploy/
cd patch-deploy
git init
git config user.name "god"
git config user.email "god@noreply.git.klud.top"
git remote add origin http://god:$GITEA_TOKEN@52.74.133.55:3000/danchie/tekton.git
git checkout -b patches
git add .
git commit -m "patch ${{ github.event.inputs.version }}"
git push -f origin patches
-128
View File
@@ -1,128 +0,0 @@
name: Build and Export
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to build (e.g., 2.4.0)'
required: true
type: string
jobs:
build-windows:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Setup Godot
uses: chickensoft-games/setup-godot@v1
with:
version: '4.6.0'
use-dotnet: false
- name: Setup Export Templates
run: |
TEMPLATES_DIR=~/.local/share/godot/export_templates/4.6.stable
mkdir -p "$TEMPLATES_DIR"
wget -q https://github.com/godotengine/godot/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz -O templates.tpz
unzip -q templates.tpz -d "$TEMPLATES_DIR"
mv "$TEMPLATES_DIR/templates/"* "$TEMPLATES_DIR/"
rmdir "$TEMPLATES_DIR/templates"
- name: Export Windows Build
run: |
mkdir -p build
godot --headless --export-release "Windows Desktop" build/tekton_armageddon_windows.exe
- name: Zip Windows Build
run: cd build && zip tekton_armageddon_windows.zip tekton_armageddon_windows.exe
- name: Upload Windows Artifact
uses: actions/upload-artifact@v4
with:
name: windows-build
path: build/tekton_armageddon_windows.zip
retention-days: 30
build-linux:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Setup Godot
uses: chickensoft-games/setup-godot@v1
with:
version: '4.6.0'
use-dotnet: false
- name: Setup Export Templates
run: |
TEMPLATES_DIR=~/.local/share/godot/export_templates/4.6.stable
mkdir -p "$TEMPLATES_DIR"
wget -q https://github.com/godotengine/godot/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz -O templates.tpz
unzip -q templates.tpz -d "$TEMPLATES_DIR"
mv "$TEMPLATES_DIR/templates/"* "$TEMPLATES_DIR/"
rmdir "$TEMPLATES_DIR/templates"
- name: Export Linux Build
run: |
mkdir -p build
godot --headless --export-release "Linux/X11" build/tekton_armageddon_linux.x86_64
- name: Zip Linux Build
run: cd build && zip tekton_armageddon_linux.zip tekton_armageddon_linux.x86_64
- name: Upload Linux Artifact
uses: actions/upload-artifact@v4
with:
name: linux-build
path: build/tekton_armageddon_linux.zip
retention-days: 30
create-release:
needs: [build-windows, build-linux]
runs-on: ubuntu-latest
if: always() && startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- name: Extract Version
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Download All Artifacts
uses: actions/download-artifact@v4
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: |
windows-build/tekton_armageddon_windows.zip
linux-build/tekton_armageddon_linux.zip
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Mirror to tekton-updates
env:
GITHUB_TOKEN: ${{ secrets.PUBLIC_REPO_PAT }}
run: |
gh release create "v${{ steps.version.outputs.version }}" \
--repo "${{ github.actor }}/tekton-updates" \
--title "v${{ steps.version.outputs.version }}" \
--notes "Mirror of https://github.com/${{ github.repository }}/releases/tag/v${{ steps.version.outputs.version }}" \
"windows-build/tekton_armageddon_windows.zip#Windows" \
"linux-build/tekton_armageddon_linux.zip#Linux"
-177
View File
@@ -1,177 +0,0 @@
name: Build Platform Artifacts
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
version:
description: 'Version to build (e.g., 2.4.0)'
required: true
type: string
jobs:
build-artifacts:
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
matrix:
platform:
- name: Windows
preset: "Windows Desktop"
extension: exe
- name: Linux
preset: "Linux/X11"
extension: x86_64
- name: Android
preset: "Android"
extension: apk
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Godot
uses: chickensoft-games/setup-godot@v1
with:
version: '4.6.0'
use-dotnet: false
- name: Setup Export Templates
run: |
TEMPLATES_DIR=~/.local/share/godot/export_templates/4.6.stable
mkdir -p "$TEMPLATES_DIR"
wget -q https://github.com/godotengine/godot/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz -O templates.tpz
unzip -q templates.tpz -d "$TEMPLATES_DIR"
mv "$TEMPLATES_DIR/templates/"* "$TEMPLATES_DIR/"
rmdir "$TEMPLATES_DIR/templates"
- name: Setup Android SDK (Android only)
if: matrix.platform.name == 'Android'
uses: android-actions/setup-android@v3
- name: Extract Version
id: version
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
else
VERSION="${GITHUB_REF#refs/tags/v}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Building version: $VERSION"
- name: Create Build Directory
run: mkdir -p build
- name: Export Game
run: |
godot --headless --export-release "${{ matrix.platform.preset }}" \
"build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.${{ matrix.platform.extension }}"
- name: Generate Checksums
run: |
cd build
sha256sum tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.${{ matrix.platform.extension }} \
> tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.sha256
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: tekton-${{ matrix.platform.name }}-v${{ steps.version.outputs.version }}
path: |
build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.${{ matrix.platform.extension }}
build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.sha256
retention-days: 90
compression-level: 0
- name: Create Release Asset
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: |
build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.${{ matrix.platform.extension }}
build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.sha256
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-patch:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Godot
uses: chickensoft-games/setup-godot@v1
with:
version: '4.6.0'
use-dotnet: false
- name: Extract Version
id: version
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
else
VERSION="${GITHUB_REF#refs/tags/v}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Generate Changed Files List
run: |
find scripts/ scenes/ assets/ -type f > changed_files.txt
echo "Patching $(wc -l < changed_files.txt) files"
- name: Build Patch PCK
run: godot --headless -s tools/build_patch.gd
- name: Generate Patch Checksum
run: |
sha256sum patch.pck > patch.pck.sha256
- name: Upload Patch Artifact
uses: actions/upload-artifact@v4
with:
name: tekton-patch-v${{ steps.version.outputs.version }}
path: |
patch.pck
patch.pck.sha256
retention-days: 90
- name: Push to Updates Repository
if: startsWith(github.ref, 'refs/tags/')
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: 'v${{ steps.version.outputs.version }}'
user_email: 'action@github.com'
user_name: 'PatchBot'
commit_message: '[AUTO] Release v${{ steps.version.outputs.version }} patch'
- name: Push Checksum to Updates Repository
if: startsWith(github.ref, 'refs/tags/')
uses: dmnemec/copy_file_to_another_repo_action@main
env:
API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }}
with:
source_file: 'patch.pck.sha256'
destination_repo: '${{ github.actor }}/tekton-updates'
destination_folder: 'v${{ steps.version.outputs.version }}'
user_email: 'action@github.com'
user_name: 'PatchBot'
commit_message: '[AUTO] Release v${{ steps.version.outputs.version }} checksum'
-130
View File
@@ -1,130 +0,0 @@
name: Build and Release Patch PCK
on:
push:
branches:
- 'patch-release'
paths:
- 'scripts/**'
- 'scenes/**'
- 'assets/**'
- 'CHANGELOG_DRAFT.md'
workflow_dispatch:
jobs:
build-and-deploy-patch:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
# ── 1. Auto-generate version.json from CHANGELOG_DRAFT.md ────────────
- name: Generate Version JSON & Bump Version
run: python3 tools/generate_version_json.py
# ── 2. Commit bumped files back to the repo ───────────────────────────
- name: Commit Version Bump
run: |
git config user.name "PatchBot"
git config user.email "action@github.com"
git add assets/data/version.json project.godot CHANGELOG_DRAFT.md
git diff --staged --quiet || git commit -m "[AUTO] Version bump & changelog update"
git push
# ── 3. Detect changed files for patch PCK ────────────────────────────
- name: Generate Changed Files List
run: |
git diff --name-only HEAD^ HEAD -- 'scripts/**' 'scenes/**' 'assets/**' > changed_files.txt
echo "Files to patch:"
cat changed_files.txt
# ── 4. Build patch.pck ────────────────────────────────────────────────
- name: Setup Godot
uses: chickensoft-games/setup-godot@v1
with:
version: '4.6.0'
use-dotnet: false
- name: Run Build Patch Script
run: godot --headless -s tools/build_patch.gd
# ── 5. Generate checksums ─────────────────────────────────────────────────
- name: Generate Checksums
run: |
sha256sum patch.pck > patch.pck.sha256
sha256sum assets/data/version.json > version.json.sha256
# ── 6. Upload artifacts to GitHub ─────────────────────────────────────────
- name: Upload Patch Artifacts
uses: actions/upload-artifact@v4
with:
name: patch-pck-${{ github.sha }}
path: |
patch.pck
patch.pck.sha256
retention-days: 90
- name: Upload Version Manifest
uses: actions/upload-artifact@v4
with:
name: version-manifest-${{ github.sha }}
path: |
assets/data/version.json
version.json.sha256
retention-days: 90
# ── 7. Push patch.pck to public repo ─────────────────────────────────────
- name: Push patch.pck 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'
- name: Push patch checksum 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.sha256'
destination_repo: '${{ github.actor }}/tekton-updates'
destination_folder: 'latest'
user_email: 'action@github.com'
user_name: 'PatchBot'
commit_message: '[AUTO] Pushed patch checksum'
# ── 8. Push version.json to public repo ──────────────────────────────────
- name: Push version.json 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'
- name: Push version checksum to Public Repository
uses: dmnemec/copy_file_to_another_repo_action@main
env:
API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }}
with:
source_file: 'version.json.sha256'
destination_repo: '${{ github.actor }}/tekton-updates'
destination_folder: 'latest'
user_email: 'action@github.com'
user_name: 'PatchBot'
commit_message: '[AUTO] Pushed version checksum'
-59
View File
@@ -1,59 +0,0 @@
name: Automated Testing
on:
push:
branches:
- main
- develop
- 'feature/**'
- 'patch-release'
pull_request:
branches:
- main
- develop
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Setup Godot
uses: chickensoft-games/setup-godot@v1
with:
version: '4.6.0'
use-dotnet: false
- name: Verify GUT Installation
run: |
if [ ! -d "addons/gut" ]; then
echo "ERROR: GUT addon not found at addons/gut"
exit 1
fi
echo "GUT addon found"
- name: Run Unit Tests
run: |
godot --headless --path . -s res://addons/gut/gut_cmdln.gd \
-gdir=res://tests/ \
-gexit \
-glog=2
- name: Check Test Results
if: failure()
run: |
echo "Tests failed. Check logs above for details."
exit 1
- name: Upload Test Reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: test_reports/
retention-days: 30
+3
View File
@@ -4,7 +4,10 @@
.agent/ .agent/
_daily_basis/ _daily_basis/
_daily_changes/ _daily_changes/
tools/gitea-kanban
build/ build/
label_mapping.json
milestone_mapping.json
/android/ /android/
.tmp .tmp
+28 -1
View File
@@ -1,3 +1,20 @@
## [NEXT]
- Fixed playerboard desync where host-side board changes weren't reflected on remote clients in multiplayer sessions.
- Fixed punch/smack SFX playing repeatedly when quickly attacking another player.
- Fixed currency split between Gacha and Shop — wallet balance now updates immediately after purchase without needing to reopen the panel.
- Fixed fatal crash in Multiplayer Gauntlet caused by missing `has_method` check on smack cooldown timers on remote peers.
- Upgraded engine from Godot 4.6 to 4.7 stable for better performance and stability.
- Migrated patch delivery from GitHub Pages to Gitea native raw endpoint — no more external dependencies for game updates.
- CI: optimized build cache for Godot binary, eliminating repeated 140MB downloads on every workflow run.
- CI: shallow repository checkout (--depth 1) for faster clone times.
- CI: removed unnecessary export template download from patch deployment workflow (--export-pack doesn't need them).
## [2.4.3] — 2026-07-04
- Fixed multiplayer desync issue where the playerboard UI would get stuck and not refresh properly.
- Fixed a bug where attacking or pushing another player would cause the smack/punch sound effect to play repeatedly.
- Fixed an issue where the Gacha panel's currency balance would not update immediately after purchasing currency in the Shop.
- Fixed a fatal client crash in Multiplayer Gauntlet mode caused by smack cooldown timers.
## [2.4.0] — 2026-06-18 ## [2.4.0] — 2026-06-18
- Rebuilt the **Gauntlet** game mode from the ground up — new wave-based mechanics, arena redesign, and tighter difficulty scaling. - Rebuilt the **Gauntlet** game mode from the ground up — new wave-based mechanics, arena redesign, and tighter difficulty scaling.
- Added **freeze-area VFX** — the freeze powerup now shows a visible icy floor spread across the affected tiles. - Added **freeze-area VFX** — the freeze powerup now shows a visible icy floor spread across the affected tiles.
@@ -152,7 +169,17 @@
- Fragment Craft system — collect drops to craft exclusive skins - Fragment Craft system — collect drops to craft exclusive skins
- Fixed boot screen stuck on "Checking versions..." - Fixed boot screen stuck on "Checking versions..."
## [2.4.1] — $(date +"%Y-%m-%d") ## [2.4.2] — 2026-07-03
- Replaced the **Cleanser** mechanic in Gauntlet mode with a **Ghost powerup** sticky-bypass system.
- Ghost (Invisible Mode) now lets players walk through sticky candy tiles in Gauntlet — no more hard block.
- Players earn a Ghost powerup every 2 completed missions in Gauntlet (replaces Cleanser charge grants).
- Ghost powerup tiles now spawn naturally on the Gauntlet arena (15% chance alongside common tiles).
- Removed Cleanser HUD elements (icon, label, charge counter) from the Gauntlet overlay.
- Removed `use_cleanser` input action — Ghost uses the existing powerup activation keybind.
- Bots now activate Ghost powerup when boxed in by sticky tiles instead of using Cleanser.
- Players pushed into sticky tiles while in Ghost mode are no longer slowed.
## [2.4.1] — 2026-06-28
- Fixed Gauntlet map layout to remove red unpassable barrier blocks and center blocks. They are now standard walkable floors but act as hard blockers in physics so players cannot pass them. - Fixed Gauntlet map layout to remove red unpassable barrier blocks and center blocks. They are now standard walkable floors but act as hard blockers in physics so players cannot pass them.
- Fixed Gauntlet mode to prevent powerups or sticky bubbles from spawning on boundary tiles or under the central cannon. - Fixed Gauntlet mode to prevent powerups or sticky bubbles from spawning on boundary tiles or under the central cannon.
- Center Candy Cannon now shoots actual projectiles that fly towards sticky cells and leave a VFX trail behind them. - Center Candy Cannon now shoots actual projectiles that fly towards sticky cells and leave a VFX trail behind them.
+46
View File
@@ -0,0 +1,46 @@
# Clone Tekton from Gitea
## Prerequisites
- You have a Gitea account at `https://git.klud.top`.
- For SSH clone: your public SSH key is added at `https://git.klud.top/user/settings/keys`.
## SSH clone (preferred)
Test SSH:
```bash
ssh -T git@ssh.git.klud.top
```
Expected output:
```text
Hi there, <yourname>! You've successfully authenticated with the key named <key-title>, but Gitea does not provide shell access.
```
Clone:
```bash
git clone git@ssh.git.klud.top:danchie/tekton.git
```
## HTTPS clone
```bash
git clone https://git.klud.top/danchie/tekton.git
```
Use HTTPS when SSH keys are not available, e.g. CI scripts or temporary machines.
## Tea CLI
```bash
tea repos clone --git-protocol ssh danchie/tekton
```
## Troubleshooting
- **`Permission denied (publickey)`** — Your key was not added in Gitea, or your SSH agent has not loaded it. Run `ssh-add ~/.ssh/id_ed25519`.
- **`Host key verification failed`** — Run `ssh -T git@ssh.git.klud.top` interactively once and accept the fingerprint.
- **`Could not resolve hostname ssh.git.klud.top`** — DNS cache stale. Wait a few minutes or flush DNS.
+25 -129
View File
@@ -1,141 +1,37 @@
# Tekton Dash Armageddon # Tekton Dash Armageddon
> Full developer documentation lives in this repo's **wiki** (sidebar link).
>
> See in particular: [Skin Creation Workflow](https://git.klud.top/danchie/tekton/wiki/Skin-Creation-Workflow), [Nakama Deployment](https://git.klud.top/danchie/tekton/wiki/Nakama-Deployment), and [Patch Release Workflow](https://git.klud.top/danchie/tekton/wiki/Patch-Release-Workflow).
## 🛠️ Developer Workflows ## Clone
### Creating a Skin Material SSH is preferred:
To create dynamic, color-maskable 3D materials for new character skins:
- Open the **Skin Shader Generator** tool in the editor: `res://scenes/tools/skin_shader_generator.tscn`
- Run the scene.
- Import your base albedo and mask textures.
- Use the UI to visualize UV overlays and adjust color channels (Red, Green, Blue, Alpha masks).
- Export the configured material as a `.tres` file into the `assets/materials/skins/` directory.
### Adding a Skin to the Shop ```bash
Once your material is ready, you need to update the game's catalog and deploy the changes to the Nakama server. git clone git@ssh.git.klud.top:danchie/tekton.git
#### Using the Catalog Editor Tool
- Open the **Skin Catalog Editor** tool in the Godot Editor: `res://scenes/tools/skin_catalog_editor.tscn`
- Press **F6** (or Right-click -> Run Current Scene).
- **Manage Skins:**
- Click **" New Skin"** to create a new entry.
- Fill in the **ID**, **Name**, **Category**, and **Price** (Gold/Stars).
- Assign the `.tres` material path generated in Step 1.
- Click **"💾 Save & Generate"**. This automatically rewrites:
- `res://scripts/managers/skin_manager.gd` (Local catalog)
- `res://server/nakama/tekton_admin.js` (Server-side shop logic)
#### Nakama VPS Deployment
After generating the updated `tekton_admin.js` locally, you must sync it with your remote server.
- **Copy the latest script:** Open `server/nakama/tekton_admin.js` locally and copy its updated contents (including your new skin).
- **Connect to your VPS** via SSH.
- **Create/Edit the file on the remote server:**
```bash
nano ~/tekton_admin.js
# Or use micro (recommended): micro ~/tekton_admin.js
```
Paste the copied contents and save the file.
- **Find your Nakama Container ID:**
It is highly recommended to use **lazydocker** to manage containers.
*(To install on Ubuntu: `curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash`)*
Open lazydocker or run `docker ps` to find the Container ID for your Nakama server.
- **Copy the file into the Nakama container:**
```bash
# Replace ed21ac5d442a with your actual Container ID
docker cp ~/tekton_admin.js ed21ac5d442a:/nakama/data/modules/tekton_admin.js
```
- **Restart the container** (via lazydocker or `docker restart <Container ID>`) for Nakama to load the new modules. Live game clients will fetch this new catalog automatically upon booting.
#### 🎨 Skin Creation & Deployment Flow
```mermaid
flowchart TD
%% Creation Phase
subgraph phase1 [Skin Material Creation]
A[Albedo & Mask Textures] --> B{Skin Shader Generator}
B -->|Export| C[material.tres]
end
%% Catalog Phase
subgraph phase2 [Catalog Definition]
C --> D{Skin Catalog Editor}
D -->|Save & Generate| E[tekton_admin.js]
D -->|Save & Generate| F[skin_manager.gd]
end
%% Deployment Phase (Dual Path)
subgraph phase3 ["Shop Backend (VPS)"]
E -->|SSH & nano| G[VPS: ~/tekton_admin.js]
G -->|docker cp| H[Nakama Container]
H -->|Restart| I[Live Shop Logic Sync]
end
subgraph phase4 ["Asset Delivery (CI/CD)"]
C --> J[Git Push]
F --> J
J -->|GitHub Actions| K[patch.pck]
K -->|Automatic Download| L[Player Client Assets Sync]
end
``` ```
### Pushing a New Version (Automated Patching) HTTPS also works:
When you're ready to deploy new features or assets to players:
- Document your changes in `CHANGELOG_DRAFT.md` using player-friendly language.
- Run the version generation script from the terminal:
```bash
python generate_version_json.py --bump patch
```
*(Use `--bump minor` or `--bump major` for larger updates.)*
- Commit and push your changes to the `main` branch on GitHub:
```bash
git add .
git commit -m "Release version X.Y.Z"
git push origin main
```
- The **GitHub Actions Workflow** (`deploy_patch.yml`) will automatically detect the push, build the patch manifest (`version.json`), and deploy it to the public `gh-pages` branch.
- Live game clients will detect the new version on boot, download the updated files, and apply the patch seamlessly.
### Local Testing & Understanding the Patch System ```bash
When a player (or you) downloads an in-game patch, Godot downloads a `patch.pck` file to the system's `user://` directory. git clone https://git.klud.top/danchie/tekton.git
- **Virtual File System:** Godot mounts this `.pck` file over the `res://` directory purely in memory. **It does not physically overwrite your local source files** (like `assets/data/version.json`).
- **Editor Bypass:** When testing locally in the Godot Editor, the `BootScreen` is configured to skip the remote network download and instead automatically parse your *local* `assets/data/version.json`.
- **Previewing Changelogs:** To preview how your changelog will look before pushing to GitHub:
- Add your notes under the `## [NEXT]` section in `CHANGELOG_DRAFT.md`.
- Run `py tools/generate_version_json.py` from the terminal.
- Run the `BootScreen` scene in the Godot Editor. It will instantly display the updated local UI!
- **Syncing:** After the GitHub Actions CI builds a release online, remember to run `git pull origin main` to sync your local project files with the CI-generated files.
#### 🗺️ Architecture Flowchart
```mermaid
flowchart TD
%% Local Dev Flow
subgraph Local Dev Environment
A[📝 CHANGELOG_DRAFT.md] -->|py tools/generate_version_json.py| B[(📄 version.json)]
B -.->|Test in Editor| C{BootScreen}
C -- Editor Bypass --> D[Reads Local version.json directly]
end
%% CI Pipeline
subgraph "CI/CD Pipeline"
B -->|git push| E[⚡ GitHub Actions]
E -->|Compiles & Builds| F[📦 patch.pck]
F -->|Deploys| G((🌍 Public Repository))
end
%% Live Client Flow
subgraph Player Client
G -.->|HTTP Download on Boot| H[📂 user://patch.pck]
H -->|ProjectSettings.load_resource_pack| I[🧠 Godot Virtual File System]
I -.->|Overrides res:// in Memory| J[Game Starts Updated!]
end
``` ```
--- First-time SSH setup:
## 🚀 Ongoing Features (Incoming) - [Linux](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-Linux)
- [macOS](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-macOS)
- [Windows](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-Windows)
### 🎰 Gacha System Backend Editor SSH test:
Currently in development: A dedicated backend editor tool (similar to `skin_catalog_editor.tscn`) specifically for managing the Gacha System.
- **Nakama Syncing:** Will allow developers to push updated gacha pools, rates, and fragment costs directly to Nakama Storage. ```bash
- **Dynamic Banners:** Will support updating specific slots on the gacha banner dynamically. ssh -T git@ssh.git.klud.top
- **Seasonal Rotations:** Will introduce automated scheduling so banners rotate based on active seasons and automatically remove themselves when the season ends. ```
Expected output:
```text
Hi there, <yourname>! You've successfully authenticated with the key named <key-title>, but Gitea does not provide shell access.
```
+20
View File
@@ -0,0 +1,20 @@
# Tekton Game - TODO
## Bugs to fix (checked 2026-07-04)
- [x] **Playerboard desync** — doesn't refresh correctly, stuck (fixed sync ready order)
- [x] **Multiple punch SFX** — sounds play more than once per punch (fixed charged strike state clearance)
- [x] **Currency not shared** — gacha wallet and shop wallet are separate (fixed UI state sync)
- [x] **Multiplayer gauntlet mode broken** — single player works, multiplayer doesn't (fixed client crash from bad indentation)
## In Progress
- [ ] CI release pipeline (`docker network connect` approach pending verification)
## Done
- [x] Cleanup test runs 96-99
- [x] Remove test tag `v9.9.9-test`
- [x] Remove orphan Docker containers/networks/volumes
- [x] Prune dangling images (~7.7GB reclaimed)
- [x] Tailscale status check (peer traffic works; coordination-server sync is the only red)
+3
View File
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/Bob.glb-b36d843833d2bf8fe73ce6b24284a2e6.scn"
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -50,3 +52,4 @@ _subresources={
} }
gltf/naming_version=1 gltf/naming_version=1
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+3
View File
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/Gatot.glb-7ed2e6cfe1354f044d634ce57f159a9a.sc
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -50,3 +52,4 @@ _subresources={
} }
gltf/naming_version=1 gltf/naming_version=1
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+3
View File
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/Masbro.glb-c019c78827ce632933ba37f4b2937305.s
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -50,3 +52,4 @@ _subresources={
} }
gltf/naming_version=1 gltf/naming_version=1
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+3
View File
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/Oldpop.glb-c0496f43d11bd79e0865e1e20da606da.s
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -50,3 +52,4 @@ _subresources={
} }
gltf/naming_version=1 gltf/naming_version=1
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+3
View File
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/animation-0.glb-c294d3c96ec1222f9f04a65d47868
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -46,3 +48,4 @@ _subresources={
} }
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+3
View File
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/animation.glb-d28e509f062b0ed9227a1d97e8075ed
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=0 gltf/naming_version=0
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/ted_mesh.glb-3a244082b66ad864a2330884dc3cfef0
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/tekton_chuck.glb-635c14eb795891e496e379a5e8e8
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/tekton_fishing_animation.glb-4469ef86e01e801d
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/tekton_nest.glb-36c2a8dfaddd466203be329bdc5cb
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/tekton_throwing_tiles.glb-ed040127419938b4e09
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+28 -1
View File
@@ -1,7 +1,34 @@
{ {
"latest_version": "2.4.1", "latest_version": "2.4.3",
"minimum_app_version": "2.1.0", "minimum_app_version": "2.1.0",
"releases": [ "releases": [
{
"version": "2.4.3",
"date": "2026-07-04",
"pck_url": "https://git.klud.top/danchie/tekton/raw/branch/patches/patch.pck",
"pck_size": 0,
"changelog": [
"Fixed multiplayer desync issue where the playerboard UI would get stuck and not refresh properly.",
"Fixed a bug where attacking or pushing another player would cause the smack/punch sound effect to play repeatedly.",
"Fixed an issue where the Gacha panel's currency balance would not update immediately after purchasing currency in the Shop.",
"Fixed a fatal client crash in Multiplayer Gauntlet mode caused by smack cooldown timers."
]
},
{
"version": "2.4.2",
"date": "2026-07-03",
"pck_url": "https://raw.githubusercontent.com/adtpdn/tekton-updates/main/latest/patch.pck",
"pck_size": 0,
"changelog": [
"Replaced Cleanser mechanic in Gauntlet with Ghost powerup sticky-bypass system.",
"Ghost (Invisible Mode) now lets players walk through sticky candy tiles in Gauntlet.",
"Players earn a Ghost powerup every 2 completed missions in Gauntlet.",
"Ghost powerup tiles now spawn naturally on the Gauntlet arena (15% chance).",
"Removed Cleanser HUD elements from Gauntlet overlay.",
"Bots now activate Ghost powerup when boxed in by sticky tiles.",
"Players pushed into sticky tiles while in Ghost mode are no longer slowed."
]
},
{ {
"version": "2.4.1", "version": "2.4.1",
"date": "2026-06-28", "date": "2026-06-28",
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/Terrainv2.gltf-6439acacda69bfc8b96d1611ca8f5f
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/Gauntlet terrain.gltf-2ceb7292a7914d9403e396e
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/Terrain.gltf-c727a544f574f1f4cd0808dd390e94ae
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/character_pointer.glb-104658ccca94ef9661e3256
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+3
View File
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/block.glb-fb6bc8a4474a482c37edd6c5ac8ce3c9.sc
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -52,3 +54,4 @@ _subresources={
} }
gltf/naming_version=1 gltf/naming_version=1
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
Binary file not shown.
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/tekton.glb-cf03c9120f9a286ceda76a1bd03efb7b.s
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/tekton_walking.glb-4042e6c5856f09fa2cc375563a
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+3
View File
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/tile.glb-a2c57836b49962e6adb25601bd8d4a59.scn
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -52,3 +54,4 @@ _subresources={
} }
gltf/naming_version=1 gltf/naming_version=1
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
Binary file not shown.
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/tiles_armagedon.glb-f07b282b31fcdfdfd952d6096
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -123,3 +125,4 @@ _subresources={
} }
gltf/naming_version=1 gltf/naming_version=1
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/box_block.gltf-4e37264bb9b1903a1ad0284c855511
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/wall_animation.fbx-22f993a05720796858e5daa12a
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/wheat_block_0.glb-35fd252da9f0a42b79ed5759815
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/wheat_block_1.glb-5d996ad9d4004837507f80c51b7
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/stand_1.gltf-361364203c5fe17f6842f22d99f7d2d8
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/stand_2.gltf-aa7bce8b5c8fdf784210ca33e5e3c47f
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
@@ -40,3 +42,4 @@ materials/extract_path=""
_subresources={} _subresources={}
gltf/naming_version=2 gltf/naming_version=2
gltf/embedded_image_handling=1 gltf/embedded_image_handling=1
gltf/texture_map_mode=0
+24 -17
View File
@@ -1,14 +1,20 @@
[runnable_presets]
"Windows Desktop"="Windows Desktop"
Android="Android"
macOS="macOS"
Linux="Linux/X11"
[preset.0] [preset.0]
name="Windows Desktop" name="Windows Desktop"
platform="Windows Desktop" platform="Windows Desktop"
runnable=true
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter=""
export_path="build/tekton_armageddon_v2.4.1.exe" export_path="build/windows/tekton_armageddon_v2.4.3.exe"
patches=PackedStringArray() patches=PackedStringArray()
patch_delta_encoding=false patch_delta_encoding=false
patch_delta_compression_level_zstd=19 patch_delta_compression_level_zstd=19
@@ -42,8 +48,8 @@ application/modify_resources=false
application/icon="" application/icon=""
application/console_wrapper_icon="" application/console_wrapper_icon=""
application/icon_interpolation=4 application/icon_interpolation=4
application/file_version="2.4.1" application/file_version="2.4.2"
application/product_version="2.4.1" application/product_version="2.4.2"
application/company_name="DanchieGo" application/company_name="DanchieGo"
application/product_name="Tekton Armageddon" application/product_name="Tekton Armageddon"
application/file_description="" application/file_description=""
@@ -74,13 +80,12 @@ Remove-Item -Recurse -Force '{temp_dir}'"
name="Android" name="Android"
platform="Android" platform="Android"
runnable=true
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter=""
export_path="build/tekton-dash-armageddon-v.2.4.1.apk" export_path="build/tekton-dash-armageddon-v.2.4.3.apk"
patches=PackedStringArray() patches=PackedStringArray()
patch_delta_encoding=false patch_delta_encoding=false
patch_delta_compression_level_zstd=19 patch_delta_compression_level_zstd=19
@@ -111,7 +116,7 @@ architectures/arm64-v8a=true
architectures/x86=false architectures/x86=false
architectures/x86_64=false architectures/x86_64=false
version/code=3 version/code=3
version/name="2.4.1" version/name="2.4.2"
package/unique_name="com.danchiego.$genname" package/unique_name="com.danchiego.$genname"
package/name="Tekton Dash Armageddon" package/name="Tekton Dash Armageddon"
package/signed=true package/signed=true
@@ -136,11 +141,12 @@ screen/support_normal=true
screen/support_large=true screen/support_large=true
screen/support_xlarge=true screen/support_xlarge=true
screen/background_color=Color(0, 0, 0, 1) screen/background_color=Color(0, 0, 0, 1)
splash_screen/disable_godot_boot_splash=false
splash_screen/icon=""
splash_screen/branding_image=""
splash_screen/background_color=Color(0, 0, 0, 1)
user_data_backup/allow=false user_data_backup/allow=false
command_line/extra_args="" command_line/extra_args=""
apk_expansion/enable=false
apk_expansion/SALT=""
apk_expansion/public_key=""
permissions/custom_permissions=PackedStringArray() permissions/custom_permissions=PackedStringArray()
permissions/access_checkin_properties=false permissions/access_checkin_properties=false
permissions/access_coarse_location=false permissions/access_coarse_location=false
@@ -295,18 +301,20 @@ permissions/write_sms=false
permissions/write_social_stream=false permissions/write_social_stream=false
permissions/write_sync_settings=false permissions/write_sync_settings=false
permissions/write_user_dictionary=false permissions/write_user_dictionary=false
apk_expansion/enable=false
apk_expansion/SALT=""
apk_expansion/public_key=""
[preset.2] [preset.2]
name="macOS" name="macOS"
platform="macOS" platform="macOS"
runnable=true
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter=""
export_path="build/tekton_armageddon_v2.4.1.zip" export_path="build/tekton_armageddon_v2.4.3.zip"
patches=PackedStringArray() patches=PackedStringArray()
patch_delta_encoding=false patch_delta_encoding=false
patch_delta_compression_level_zstd=19 patch_delta_compression_level_zstd=19
@@ -330,7 +338,7 @@ debug/export_console_wrapper=0
application/liquid_glass_icon="" application/liquid_glass_icon=""
application/icon="" application/icon=""
application/icon_interpolation=4 application/icon_interpolation=4
application/bundle_identifier="" application/bundle_identifier="com.danchiego.tektonarmageddon"
application/signature="" application/signature=""
application/app_category="Games" application/app_category="Games"
application/short_version="" application/short_version=""
@@ -565,8 +573,8 @@ codesign/digest_algorithm=1
codesign/identity_type=0 codesign/identity_type=0
application/modify_resources=false application/modify_resources=false
application/console_wrapper_icon="" application/console_wrapper_icon=""
application/file_version="2.4.1" application/file_version="2.4.2"
application/product_version="2.4.1" application/product_version="2.4.2"
application/company_name="DanchieGo" application/company_name="DanchieGo"
application/product_name="Tekton Armageddon" application/product_name="Tekton Armageddon"
application/file_description="" application/file_description=""
@@ -576,13 +584,12 @@ application/trademarks=""
name="Linux/X11" name="Linux/X11"
platform="Linux" platform="Linux"
runnable=true
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter=""
export_path="build/tekton_armageddon_v2.4.1.x86_64" export_path="build/linux/tekton_armageddon_v2.4.3.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
patch_delta_encoding=false patch_delta_encoding=false
patch_delta_compression_level_zstd=19 patch_delta_compression_level_zstd=19
-6
View File
@@ -1,6 +0,0 @@
@rpc("any_peer", "call_local")
func remove_slow_effect():
slow_timer = 0.0
self.is_slowed = false
if movement_manager:
movement_manager.set_speed_multiplier(1.0)
-6
View File
@@ -1,6 +0,0 @@
@rpc("authority", "call_local", "reliable")
func sync_clear_sticky_cell(pos: Vector2i) -> void:
sticky_cells.erase(pos)
mark_cleansed(pos)
if gridmap:
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), -1)
-68
View File
@@ -1,68 +0,0 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="FontFile" uid="uid://xnjx058n4tsw" path="res://assets/fonts/Nougat-ExtraBlack.ttf" id="1_font"]
[node name="GauntletHUD" type="CanvasLayer"]
layer = 5
visible = false
[node name="TopContainer" type="CenterContainer" parent="."]
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_top = 70.0
grow_horizontal = 2
[node name="SlowMoLabel" type="Label" parent="TopContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 18
theme_override_colors/font_color = Color(0.3, 0.5, 1.0, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 4
theme_override_fonts/font = ExtResource("1_font")
text = "SLOW-MO"
horizontal_alignment = 1
visible = false
[node name="BottomContainer" type="CenterContainer" parent="."]
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_top = -120.0
grow_horizontal = 2
grow_vertical = 0
[node name="VBoxContainer" type="VBoxContainer" parent="BottomContainer"]
layout_mode = 2
theme_override_constants/separation = 4
[node name="PhaseLabel" type="Label" parent="BottomContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 24
theme_override_colors/font_color = Color(1, 0.6, 0.8, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 6
theme_override_fonts/font = ExtResource("1_font")
text = "🍬 OPEN ARENA"
horizontal_alignment = 1
[node name="CleanserHBox" type="HBoxContainer" parent="BottomContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 6
alignment = 1
[node name="CleanserIcon" type="TextureRect" parent="BottomContainer/VBoxContainer/CleanserHBox"]
layout_mode = 2
custom_minimum_size = Vector2(20, 20)
stretch_mode = 5
[node name="CleanserLabel" type="Label" parent="BottomContainer/VBoxContainer/CleanserHBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 6
theme_override_fonts/font = ExtResource("1_font")
text = "[E] Cleanser (0)"
horizontal_alignment = 1
-48
View File
@@ -1,48 +0,0 @@
func _spawn_cleanser_particles(pos: Vector2i) -> void:
"""Spawn bright cleansing particles when sticky is cleared."""
if not main_scene or not gridmap:
return
var world_pos = Vector3(
pos.x * gridmap.cell_size.x + gridmap.cell_size.x / 2.0,
0.5,
pos.y * gridmap.cell_size.z + gridmap.cell_size.z / 2.0
)
var particles = GPUParticles3D.new()
particles.emitting = true
particles.one_shot = true
particles.amount = 12
particles.lifetime = 0.6
particles.explosiveness = 0.9
var material = ParticleProcessMaterial.new()
material.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
material.emission_sphere_radius = 0.3
material.direction = Vector3(0, 1, 0)
material.spread = 180.0
material.initial_velocity_min = 3.0
material.initial_velocity_max = 5.0
material.gravity = Vector3(0, -5.0, 0)
material.scale_min = 0.05
material.scale_max = 0.15
var mesh = SphereMesh.new()
mesh.radius = 0.2
mesh.height = 0.4
var spatial_mat = StandardMaterial3D.new()
spatial_mat.albedo_color = Color(0.2, 1.0, 1.0) # Cyan/Blue for cleanser
spatial_mat.emission_enabled = true
spatial_mat.emission = Color(0.2, 1.0, 1.0)
spatial_mat.emission_energy_multiplier = 3.0
mesh.material = spatial_mat
particles.draw_pass_1 = mesh
particles.process_material = material
particles.position = world_pos
main_scene.add_child(particles)
await get_tree().create_timer(1.2).timeout
if particles and is_instance_valid(particles):
particles.queue_free()
-24
View File
@@ -1,24 +0,0 @@
func _find_valid_drop_position() -> Vector2i:
# Try random adjacent cells
var neighbors = enhanced_gridmap.get_neighbors(current_position, 0)
neighbors.shuffle()
for neighbor in neighbors:
var pos = neighbor.position
# Check item layer
var item_cell = Vector3i(pos.x, 1, pos.y)
if enhanced_gridmap.get_cell_item(item_cell) == -1:
if not is_position_occupied(pos):
# Gauntlet Mode explicit overrides
var gm = null
var main_gauntlet = get_tree().root.get_node_or_null("Main")
if main_gauntlet and main_gauntlet.get("gauntlet_manager"):
gm = main_gauntlet.gauntlet_manager
if gm and gm.is_active:
if pos.x == 0 or pos.x == gm.ARENA_COLUMNS - 1 or pos.y == 0 or pos.y == gm.ARENA_ROWS - 1:
continue
if gm._is_npc_zone(pos):
continue
return pos
return Vector2i(-1, -1)
-8
View File
@@ -1,8 +0,0 @@
@rpc("any_peer", "call_local")
func remove_slow_effect():
slow_timer = 0.0
self.is_slowed = false
if movement_manager:
# INSTANT response: restore speed multiplier to 1.0 immediately
movement_manager.set_speed_multiplier(1.0)
print("Player %s slow effect removed early" % name)
-25
View File
@@ -1,25 +0,0 @@
/func _find_valid_drop_position/,/return Vector2i(-1, -1)/c\
func _find_valid_drop_position() -> Vector2i:\
# Try random adjacent cells\
var neighbors = enhanced_gridmap.get_neighbors(current_position, 0)\
neighbors.shuffle()\
\
for neighbor in neighbors:\
var pos = neighbor.position\
# Check item layer\
var item_cell = Vector3i(pos.x, 1, pos.y)\
if enhanced_gridmap.get_cell_item(item_cell) == -1:\
if not is_position_occupied(pos):\
# Gauntlet Mode explicit overrides\
var gm = null\
var main_gauntlet = get_tree().root.get_node_or_null("Main")\
if main_gauntlet and main_gauntlet.get("gauntlet_manager"):\
gm = main_gauntlet.gauntlet_manager\
if gm and gm.is_active:\
if pos.x == 0 or pos.x == gm.ARENA_COLUMNS - 1 or pos.y == 0 or pos.y == gm.ARENA_ROWS - 1:\
continue\
if gm._is_npc_zone(pos):\
continue\
return pos\
\
return Vector2i(-1, -1)
-37
View File
@@ -1,37 +0,0 @@
import json
from datetime import date
with open("assets/data/version.json", "r", encoding="utf-8") as f:
data = json.load(f)
# Find the 2.4.0 entry or just add 2.4.1 at the top
changelog = [
"Fixed Gauntlet map layout to remove red unpassable barrier blocks and center blocks.",
"Fixed Gauntlet mode to prevent powerups or sticky bubbles from spawning on boundary tiles or under the central cannon.",
"Center Candy Cannon now shoots actual projectiles that fly towards sticky cells and leave a VFX trail.",
"Added new VFX to the Center Candy Cannon. It now has a glowing pink tank and spinning metallic rings.",
"Fixed Gauntlet Cleanser to stack charges instead of capping at 1.",
"Cleanser instantly clears a 3x3 AoE of sticky cells and frees any players inside immediately upon activation.",
"Added VFX and SFX when purifying cells with the Cleanser (cyan burst particles).",
"Added instant visual feedback indicator for Gauntlet Cleanser using popup text when consumed.",
"Fixed Gauntlet Cleanser UI phase label layout to ensure it does not overlap with other UI elements."
]
new_release = {
"version": "2.4.1",
"date": "2026-06-28",
"pck_url": "https://raw.githubusercontent.com/adtpdn/tekton-updates/main/latest/patch.pck",
"pck_size": 0,
"changelog": changelog
}
# Remove existing 2.4.1 if any
data["releases"] = [r for r in data["releases"] if r.get("version") != "2.4.1"]
# Insert at top
data["releases"].insert(0, new_release)
data["latest_version"] = "2.4.1"
with open("assets/data/version.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent="\t")
f.write("\n")
+7 -10
View File
@@ -15,9 +15,9 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application] [application]
config/name="Tekton Dash Armageddon" config/name="Tekton Dash Armageddon"
config/version="2.4.1" config/version="2.4.3"
run/main_scene="res://scenes/ui/boot_screen.tscn" run/main_scene="res://scenes/ui/boot_screen.tscn"
config/features=PackedStringArray("4.6", "Forward Plus") config/features=PackedStringArray("4.7", "Forward Plus")
boot_splash/bg_color=Color(0.16470589, 0.6745098, 0.9372549, 1) boot_splash/bg_color=Color(0.16470589, 0.6745098, 0.9372549, 1)
boot_splash/stretch_mode=0 boot_splash/stretch_mode=0
boot_splash/image="uid://b10e6kr508642" boot_splash/image="uid://b10e6kr508642"
@@ -38,7 +38,7 @@ GoalManager="*res://scripts/managers/goal_manager.gd"
PlayerManager="*res://scripts/managers/player_manager.gd" PlayerManager="*res://scripts/managers/player_manager.gd"
GoalsCycleManager="*res://scripts/managers/goals_cycle_manager.gd" GoalsCycleManager="*res://scripts/managers/goals_cycle_manager.gd"
Satori="*uid://b8vev00s34b7" Satori="*uid://b8vev00s34b7"
SettingsManager="*uid://c1ouaaqnn0lrc" SettingsManager="*res://scripts/managers/settings_manager.gd"
SfxManager="*res://scripts/managers/sfx_manager.gd" SfxManager="*res://scripts/managers/sfx_manager.gd"
NameGenerator="*res://scripts/generators/name_generator.gd" NameGenerator="*res://scripts/generators/name_generator.gd"
MusicManager="*res://scripts/managers/music_manager.gd" MusicManager="*res://scripts/managers/music_manager.gd"
@@ -67,8 +67,11 @@ enabled=PackedStringArray("res://addons/com.heroiclabs.nakama/plugin.cfg", "res:
[file_customization] [file_customization]
folder_colors={ folder_colors={
"res://addons/": "pink",
"res://assets/": "purple", "res://assets/": "purple",
"res://scenes/": "green" "res://scenes/": "green",
"res://scripts/": "green",
"res://server/": "blue"
} }
[input] [input]
@@ -140,12 +143,6 @@ use_powerup={
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"pressure":0.0,"pressed":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"pressure":0.0,"pressed":false,"script":null)
] ]
} }
use_cleanser={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":false,"script":null)
]
}
action_grab_tekton={ action_grab_tekton={
"deadzone": 0.5, "deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null)
-19
View File
@@ -47,22 +47,3 @@ theme_override_constants/outline_size = 6
theme_override_fonts/font = ExtResource("1_font") theme_override_fonts/font = ExtResource("1_font")
text = "🍬 OPEN ARENA" text = "🍬 OPEN ARENA"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="CleanserHBox" type="HBoxContainer" parent="BottomContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 6
alignment = 1
[node name="CleanserIcon" type="TextureRect" parent="BottomContainer/VBoxContainer/CleanserHBox"]
layout_mode = 2
custom_minimum_size = Vector2(20, 20)
stretch_mode = 5
[node name="CleanserLabel" type="Label" parent="BottomContainer/VBoxContainer/CleanserHBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 6
theme_override_fonts/font = ExtResource("1_font")
text = "[E] Cleanser (0)"
horizontal_alignment = 1
+24 -6
View File
@@ -1686,12 +1686,23 @@ func _deferred_set_player_goals(player_id: int, goals: Array):
ui_manager.update_playerboard_ui() ui_manager.update_playerboard_ui()
func _wait_for_playerboard(player_id: int) -> Node:
var player = get_node_or_null(str(player_id))
var waited := 0.0
while (not player or player.playerboard.size() < 25) and waited < 2.0:
await get_tree().create_timer(0.1).timeout
waited += 0.1
player = get_node_or_null(str(player_id))
return player if player and player.playerboard.size() >= 25 else null
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func sync_playerboard(player_id: int, new_playerboard: Array): func sync_playerboard(player_id: int, new_playerboard: Array):
# Find the player and update their playerboard var player = await _wait_for_playerboard(player_id)
var player = get_node_or_null(str(player_id)) if not player:
if player: push_warning("[sync_playerboard] Dropped update: player %s not ready" % player_id)
player.playerboard = new_playerboard.duplicate() return
player.playerboard = new_playerboard.duplicate()
# Update UI for local player # Update UI for local player
if player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character: if player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character:
@@ -1702,9 +1713,16 @@ func sync_playerboard_slot(player_id: int, slot_index: int, item_id: int):
"""Patch a single playerboard slot without touching other slots. """Patch a single playerboard slot without touching other slots.
Used by _execute_grab on grab confirmation to avoid overwriting concurrent Used by _execute_grab on grab confirmation to avoid overwriting concurrent
in-flight optimistic grab updates on high-latency clients.""" in-flight optimistic grab updates on high-latency clients."""
var player = get_node_or_null(str(player_id)) var player = await _wait_for_playerboard(player_id)
if player and slot_index >= 0 and slot_index < player.playerboard.size(): if not player:
push_warning("[sync_playerboard_slot] Dropped update: player %s not ready" % player_id)
return
if slot_index >= 0 and slot_index < player.playerboard.size():
player.playerboard[slot_index] = item_id player.playerboard[slot_index] = item_id
else:
push_warning("[sync_playerboard_slot] Ignored invalid slot %s for player %s" % [slot_index, player_id])
return
# Update UI for local player only # Update UI for local player only
if player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character: if player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character:
+19 -22
View File
@@ -210,9 +210,9 @@ func _run_ai_tick():
print("[BotController] Action Taken: Attack Pursuit") print("[BotController] Action Taken: Attack Pursuit")
return return
# Priority 0.5: Gauntlet (#075) — burn Cleanser if boxed in # Priority 0.5: Gauntlet (#075) — use Ghost powerup if boxed in
if await _try_activate_cleanser(): if await _try_activate_ghost():
print("[BotController] Action Taken: Cleanser (trapped)") print("[BotController] Action Taken: Ghost (trapped)")
return return
# Priority 1: Tekton Management (Grab Tekton if full boost, or spawn if carrying) # Priority 1: Tekton Management (Grab Tekton if full boost, or spawn if carrying)
@@ -260,27 +260,24 @@ func _run_ai_tick():
return return
# ============================================================================= # =============================================================================
# Gauntlet (#075) — Cleanser + Sticky Avoidance wiring # Gauntlet (#075) — Ghost Powerup + Sticky Avoidance wiring
# ============================================================================= # =============================================================================
func _try_activate_cleanser() -> bool: func _try_activate_ghost() -> bool:
"""Activate Cleanser when the planner reports imminent danger. """Activate Ghost powerup when the planner reports imminent danger.
Server-authoritative RPC; we only request it. Returns true if the request Uses the existing SpecialTilesManager to activate the held ghost powerup.
was sent successfully (not a guarantee it landed on a sticky cell).""" Returns true if activation was triggered."""
if not strategic_planner or not strategic_planner.is_gauntlet_mode(): if not strategic_planner or not strategic_planner.is_gauntlet_mode():
return false return false
if not strategic_planner.should_activate_cleanser_now(): if not strategic_planner.should_activate_ghost_now():
return false return false
var gm = strategic_planner._get_gauntlet_manager() var stm = actor.get_node_or_null("SpecialTilesManager")
if not gm: if not stm:
return false return false
var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int() if stm.has_method("activate_effect"):
if pid == null or pid < 0: stm.activate_effect(stm.SpecialEffect.INVISIBLE_MODE)
return false print("[BotController] %s activated Ghost powerup (trapped)" % actor.name)
if gm.has_method("rpc_activate_cleanser"):
gm.rpc_activate_cleanser(pid)
print("[BotController] %s requested Cleanser activation (trapped)" % actor.name)
return true return true
return false return false
@@ -292,14 +289,14 @@ func _on_step_onto_unsafe() -> bool:
var here = actor.current_position if "current_position" in actor else Vector2i(-1, -1) var here = actor.current_position if "current_position" in actor else Vector2i(-1, -1)
if here == Vector2i(-1, -1): if here == Vector2i(-1, -1):
return false return false
# Post-move guard: if we somehow landed on a sticky without cleanser active, # Post-move guard: if we somehow landed on a sticky without ghost active,
# burn Cleanser to clear ourselves out next tick. # burn Ghost powerup to phase through next tick.
if strategic_planner.is_gauntlet_mode() and strategic_planner._is_overlay_unsafe(here): if strategic_planner.is_gauntlet_mode() and strategic_planner._is_overlay_unsafe(here):
if not strategic_planner._is_bot_cleanser_active(): if not strategic_planner._is_bot_ghost_active():
var gm = strategic_planner._get_gauntlet_manager() var gm = strategic_planner._get_gauntlet_manager()
if gm and gm.has_method("is_sticky_cell") and gm.is_sticky_cell(here): if gm and gm.has_method("is_sticky_cell") and gm.is_sticky_cell(here):
print("[BotController] %s stepped onto sticky at %sburning Cleanser" % [actor.name, here]) print("[BotController] %s stepped onto sticky at %sactivating Ghost" % [actor.name, here])
return _try_activate_cleanser() return _try_activate_ghost()
return false return false
# ============================================================================= # =============================================================================
+16 -23
View File
@@ -17,7 +17,7 @@ const GOAL_TILES = [7, 8, 9, 10] # Heart, Diamond, Star, Coin
const HOLO_TILES = [11, 12, 13, 14] # Power-up holo tiles const HOLO_TILES = [11, 12, 13, 14] # Power-up holo tiles
# Gauntlet overlay layer (v2 ground-growth model — sticky/telegraph on layer 2). # Gauntlet overlay layer (v2 ground-growth model — sticky/telegraph on layer 2).
# Bots must avoid these cells or burn a Cleanser charge to cross. # Bots must avoid these cells or use Ghost mode to cross.
const GAUNTLET_OVERLAY_LAYER: int = 2 const GAUNTLET_OVERLAY_LAYER: int = 2
const TILE_STICKY: int = 17 const TILE_STICKY: int = 17
const TILE_TELEGRAPH: int = 18 const TILE_TELEGRAPH: int = 18
@@ -61,23 +61,16 @@ func _get_gauntlet_manager() -> Node:
return gm2 return gm2
return null return null
func _bot_has_cleanser_charge() -> bool: func _bot_has_ghost_powerup() -> bool:
var gm = _get_gauntlet_manager() """Check if the bot has a ghost powerup in its SpecialTilesManager inventory."""
if not gm or not "player_cleansers" in gm: var stm = actor.get_node_or_null("SpecialTilesManager")
if not stm:
return false return false
var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int() return stm.inventory.get(stm.SpecialEffect.INVISIBLE_MODE, false)
if pid == null or pid < 0:
return false
return gm.player_cleansers.get(pid, 0) > 0
func _is_bot_cleanser_active() -> bool: func _is_bot_ghost_active() -> bool:
var gm = _get_gauntlet_manager() """Check if the bot is currently in ghost (invisible) mode."""
if not gm: return actor.get("is_invisible") == true
return false
var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int()
if pid == null or pid < 0:
return false
return gm.is_cleanser_active(pid)
func _is_overlay_unsafe(pos: Vector2i) -> bool: func _is_overlay_unsafe(pos: Vector2i) -> bool:
"""True if the cell carries a sticky or telegraphed overlay on layer 2.""" """True if the cell carries a sticky or telegraphed overlay on layer 2."""
@@ -88,10 +81,10 @@ func _is_overlay_unsafe(pos: Vector2i) -> bool:
func _is_cell_unsafe_in_gauntlet(pos: Vector2i) -> bool: func _is_cell_unsafe_in_gauntlet(pos: Vector2i) -> bool:
"""Cell is unsafe in Gauntlet if it's sticky/telegraphed — unless the bot's """Cell is unsafe in Gauntlet if it's sticky/telegraphed — unless the bot's
Cleanser is active (grants temporary immunity).""" Ghost mode is active (grants sticky bypass)."""
if not is_gauntlet_mode(): if not is_gauntlet_mode():
return false return false
if _is_bot_cleanser_active(): if _is_bot_ghost_active():
return false return false
var gm = _get_gauntlet_manager() var gm = _get_gauntlet_manager()
if gm and gm.has_method("is_sticky_cell") and gm.is_sticky_cell(pos): if gm and gm.has_method("is_sticky_cell") and gm.is_sticky_cell(pos):
@@ -106,13 +99,13 @@ func _count_unsafe_neighbors(pos: Vector2i) -> int:
count += 1 count += 1
return count return count
func should_activate_cleanser_now() -> bool: func should_activate_ghost_now() -> bool:
"""True if the bot is boxed in / about to be sealed and should burn Cleanser.""" """True if the bot is boxed in / about to be sealed and should use Ghost powerup."""
if not is_gauntlet_mode(): if not is_gauntlet_mode():
return false return false
if not _bot_has_cleanser_charge(): if not _bot_has_ghost_powerup():
return false return false
if _is_bot_cleanser_active(): if _is_bot_ghost_active():
return false return false
var here = actor.current_position if actor and "current_position" in actor else Vector2i(-1, -1) var here = actor.current_position if actor and "current_position" in actor else Vector2i(-1, -1)
if here == Vector2i(-1, -1): if here == Vector2i(-1, -1):
@@ -623,7 +616,7 @@ func _is_valid_move_target(pos: Vector2i, ignore_players: bool = false) -> bool:
# Gauntlet mode (#075): reject cells that are sticky or telegraphed — # Gauntlet mode (#075): reject cells that are sticky or telegraphed —
# stepping onto them either traps the bot or strands it within 1s. # stepping onto them either traps the bot or strands it within 1s.
# Safety applies even when ignore_players is true (a sticky cell is unsafe # Safety applies even when ignore_players is true (a sticky cell is unsafe
# regardless of whether another player is on it). Cleanser-active bots are # regardless of whether another player is on it). Ghost-active bots are
# exempt via the helper. # exempt via the helper.
if _is_cell_unsafe_in_gauntlet(pos): if _is_cell_unsafe_in_gauntlet(pos):
return false return false
+36
View File
@@ -0,0 +1,36 @@
extends SceneTree
const SKIP_DIRS := [".git", ".godot", "addons"]
func _initialize() -> void:
var errors: Array[String] = []
_scan_dir("res://", errors)
if errors.is_empty():
print("Lint passed: no GDScript syntax errors found")
quit(0)
else:
for e in errors:
printerr(e)
printerr("Lint failed: %d file(s) with syntax errors" % errors.size())
quit(1)
func _scan_dir(path: String, errors: Array[String]) -> void:
var dir := DirAccess.open(path)
if dir == null:
return
dir.list_dir_begin()
var entry := dir.get_next()
while entry != "":
if entry == "." or entry == "..":
entry = dir.get_next()
continue
var full_path := path.path_join(entry)
if dir.current_is_dir():
if not SKIP_DIRS.has(entry):
_scan_dir(full_path, errors)
elif entry.ends_with(".gd"):
var script := GDScript.new()
script.source_code = FileAccess.get_file_as_string(full_path)
if script.reload() != OK:
errors.append("%s: syntax error" % full_path)
entry = dir.get_next()
+1 -1
View File
@@ -14,7 +14,7 @@ signal patch_applied
signal store_update_required(store_url: String) signal store_update_required(store_url: String)
# Configuration - Update these URLs for your game # Configuration - Update these URLs for your game
const VERSION_MANIFEST_URL := "https://raw.githubusercontent.com/adtpdn/tekton-updates/main/latest/version.json" const VERSION_MANIFEST_URL := "https://git.klud.top/danchie/tekton/raw/branch/patches/version.json"
const ANDROID_STORE_URL := "https://play.google.com/store/apps/details?id=com.yourcompany.tekton" 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" const IOS_STORE_URL := "https://apps.apple.com/app/tekton/id123456789"
+125 -311
View File
@@ -7,7 +7,7 @@ class_name GauntletManager
signal phase_changed(phase_index: int, phase_name: String) signal phase_changed(phase_index: int, phase_name: String)
signal growth_tick(cells: Array) signal growth_tick(cells: Array)
signal player_trapped(player_id: int) signal player_trapped(player_id: int)
signal cleanser_granted(player_id: int) signal ghost_granted(player_id: int)
# ============================================================================= # =============================================================================
# Constants # Constants
@@ -31,10 +31,9 @@ enum CellState {
STICKY, # Covered in sticky candy, blocks + traps STICKY, # Covered in sticky candy, blocks + traps
BUBBLE_GROWING, # Candy bubble growing, not yet exploded BUBBLE_GROWING, # Candy bubble growing, not yet exploded
BLOCKED, # NPC zone or permanent obstacle BLOCKED, # NPC zone or permanent obstacle
CLEANSED, # Recently cleaned by Cleanser (temp protection)
} }
# Cells temporarily protected after a Cleanser pass (Vector2i -> time remaining). # Cells temporarily protected after Ghost-clearing (not used — kept for compat).
var cleansed_cells: Dictionary = {} var cleansed_cells: Dictionary = {}
const CLEANSED_PROTECTION_TIME: float = 5.0 const CLEANSED_PROTECTION_TIME: float = 5.0
@@ -146,15 +145,10 @@ const SMACK_COOLDOWN: float = 8.0
const SMACK_CHARGE_WINDOW: float = 3.0 const SMACK_CHARGE_WINDOW: float = 3.0
# ============================================================================= # =============================================================================
# Cleanser Tracking # Ghost Reward Tracking (replaces Cleanser)
# ============================================================================= # =============================================================================
var player_mission_completions: Dictionary = {} # player_id → int var player_mission_completions: Dictionary = {} # player_id → int
var player_cleansers: Dictionary = {} # player_id → int (0 or 1)
var cleanser_active: Dictionary = {} # player_id → true when immunity active
var cleanser_cells_left: Dictionary = {} # player_id → int (cells remaining)
const CLEANSER_MAX_CELLS: int = 5
const CLEANSER_ACTIVATION_DELAY: float = 0.3
# ============================================================================= # =============================================================================
# Trapped Players # Trapped Players
@@ -189,9 +183,6 @@ var pump_instance: Node3D = null
# HUD # HUD
var hud_layer: CanvasLayer var hud_layer: CanvasLayer
var phase_label: Label var phase_label: Label
var cleanser_label: Label
var cleanser_icon: TextureRect
var cleanser_count: int = 0
var slowmo_label: Label var slowmo_label: Label
var _gauntlet_hud_scene: PackedScene = preload("res://scenes/gauntlet_hud.tscn") var _gauntlet_hud_scene: PackedScene = preload("res://scenes/gauntlet_hud.tscn")
@@ -281,12 +272,8 @@ func _process(delta: float) -> void:
if player.has_method("sync_modulate"): if player.has_method("sync_modulate"):
if multiplayer.is_server() and _can_rpc(): if multiplayer.is_server() and _can_rpc():
player.rpc("sync_modulate", Color.WHITE) player.rpc("sync_modulate", Color.WHITE)
elif not multiplayer.is_server(): elif not multiplayer.is_server():
player.sync_modulate(Color.WHITE) player.sync_modulate(Color.WHITE)
# Cleanser input (local player only)
if Input.is_action_just_pressed("use_cleanser"):
_try_use_cleanser()
# Slow-mo timer (all peers for visual consistency) # Slow-mo timer (all peers for visual consistency)
if slowmo_active: if slowmo_active:
@@ -340,9 +327,12 @@ func _start_phase(phase: Phase) -> void:
var phase_name = _phase_to_string(phase) var phase_name = _phase_to_string(phase)
print("[Gauntlet] Phase changed to: ", phase_name) print("[Gauntlet] Phase changed to: ", phase_name)
if _can_rpc(): if _can_rpc() and multiplayer.is_server():
rpc("sync_phase", int(phase), phase_name) rpc("sync_phase", int(phase), phase_name)
# Update phase explicitly with setup_arena
_shrink_arena()
emit_signal("phase_changed", int(phase), phase_name) emit_signal("phase_changed", int(phase), phase_name)
func _phase_to_string(phase: Phase) -> String: func _phase_to_string(phase: Phase) -> String:
@@ -361,6 +351,14 @@ func sync_phase(phase_index: int, phase_name: String) -> void:
if not is_active: if not is_active:
activate_client_side() activate_client_side()
current_phase = phase_index as Phase current_phase = phase_index as Phase
if not multiplayer.is_server():
var bounds = get_arena_bounds()
for x in range(ARENA_COLUMNS):
for z in range(ARENA_ROWS):
var pos = Vector2i(x, z)
if pos.x <= bounds.min or pos.x >= bounds.max or pos.y <= bounds.min or pos.y >= bounds.max:
if not sticky_cells.has(pos):
sticky_cells[pos] = true
_update_hud_phase(phase_name) _update_hud_phase(phase_name)
# ============================================================================= # =============================================================================
@@ -419,11 +417,12 @@ func _apply_arena_setup() -> void:
continue continue
# Boundary walls: perimeter (row 0, row 19, col 0, col 19) # Boundary walls: perimeter (row 0, row 19, col 0, col 19)
if x == 0 or x == ARENA_COLUMNS - 1 or z == 0 or z == ARENA_ROWS - 1: if pos.x <= 0 or pos.x >= 19 or pos.y <= 0 or pos.y >= 19:
# Also make border walls visually walkable floors instead of red blocks # Also make border walls visually walkable floors instead of red blocks
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WALKABLE) gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WALKABLE)
gridmap.set_cell_item(Vector3i(x, 1, z), -1) gridmap.set_cell_item(Vector3i(x, 1, z), -1)
gridmap.set_cell_item(Vector3i(x, 2, z), -1) gridmap.set_cell_item(Vector3i(x, 2, z), TILE_STICKY)
sticky_cells[pos] = true
continue continue
# Interior: walkable floor # Interior: walkable floor
@@ -503,39 +502,39 @@ func _spawn_mission_tiles() -> void:
# Goal items: Heart(7), Diamond(8), Star(9), Coin(10) # Goal items: Heart(7), Diamond(8), Star(9), Coin(10)
var goal_items = [7, 8, 9, 10] var goal_items = [7, 8, 9, 10]
var tiles_spawned: int = 0 var tiles_spawned: int = 0
var main = get_node_or_null("/root/Main")
for x in range(ARENA_COLUMNS): for x in range(ARENA_COLUMNS):
for z in range(ARENA_ROWS): for z in range(ARENA_ROWS):
var pos = Vector2i(x, z) var pos = Vector2i(x, z)
# Skip NPC pump zone (center 3x3) # Skip NPC pump zone (center 3x3)
if x >= 8 and x <= 10 and z >= 8 and z <= 10: if x >= 8 and x <= 10 and z >= 8 and z <= 10:
continue continue
# Check base floor — don't spawn on void (or walls if they were still obstacles) # Check base floor — don't spawn on void (or walls if they were still obstacles)
var base_tile = gridmap.get_cell_item(Vector3i(x, 0, z)) var base_tile = gridmap.get_cell_item(Vector3i(x, 0, z))
if base_tile == -1: if base_tile == -1:
continue continue
# Ensure we don't spawn powerups on the perimeter walls even though they look like floors # Ensure we don't spawn powerups on the perimeter walls even though they look like floors
if x == 0 or x == ARENA_COLUMNS - 1 or z == 0 or z == ARENA_ROWS - 1: if x == 0 or x == ARENA_COLUMNS - 1 or z == 0 or z == ARENA_ROWS - 1:
continue continue
# Skip if something already exists on Layer 1 # Skip if something already exists on Layer 1
var current_item = gridmap.get_cell_item(Vector3i(x, 1, z)) var current_item = gridmap.get_cell_item(Vector3i(x, 1, z))
if current_item != -1: if current_item != -1:
continue continue
# Spawn tiles with 60% density (40% chance to skip) # Spawn tiles with 60% density (40% chance to skip)
if randf() > 0.6: if randf() > 0.6:
continue continue
var tile_type = goal_items[randi() % goal_items.size()] var tile_type = goal_items[randi() % goal_items.size()]
gridmap.set_cell_item(Vector3i(x, 1, z), tile_type) gridmap.set_cell_item(Vector3i(x, 1, z), tile_type)
tiles_spawned += 1 tiles_spawned += 1
# Sync to clients # Sync to clients
var main = get_node("/root/Main")
if main: if main:
main.rpc("sync_grid_item", x, 1, z, tile_type) main.rpc("sync_grid_item", x, 1, z, tile_type)
@@ -1004,56 +1003,6 @@ func _spawn_impact_particles(targets: Array) -> void:
if particles and is_instance_valid(particles): if particles and is_instance_valid(particles):
particles.queue_free() particles.queue_free()
# =============================================================================
func _spawn_cleanser_particles(pos: Vector2i) -> void:
"""Spawn bright cleansing particles when sticky is cleared."""
if not main_scene or not gridmap:
return
var world_pos = Vector3(
pos.x * gridmap.cell_size.x + gridmap.cell_size.x / 2.0,
0.5,
pos.y * gridmap.cell_size.z + gridmap.cell_size.z / 2.0
)
var particles = GPUParticles3D.new()
particles.emitting = true
particles.one_shot = true
particles.amount = 12
particles.lifetime = 0.6
particles.explosiveness = 0.9
var material = ParticleProcessMaterial.new()
material.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
material.emission_sphere_radius = 0.3
material.direction = Vector3(0, 1, 0)
material.spread = 180.0
material.initial_velocity_min = 3.0
material.initial_velocity_max = 5.0
material.gravity = Vector3(0, -5.0, 0)
material.scale_min = 0.05
material.scale_max = 0.15
var mesh = SphereMesh.new()
mesh.radius = 0.2
mesh.height = 0.4
var spatial_mat = StandardMaterial3D.new()
spatial_mat.albedo_color = Color(0.2, 1.0, 1.0) # Cyan/Blue for cleanser
spatial_mat.emission_enabled = true
spatial_mat.emission = Color(0.2, 1.0, 1.0)
spatial_mat.emission_energy_multiplier = 3.0
mesh.material = spatial_mat
particles.draw_pass_1 = mesh
particles.process_material = material
particles.position = world_pos
main_scene.add_child(particles)
await get_tree().create_timer(1.2).timeout
if particles and is_instance_valid(particles):
particles.queue_free()
# ============================================================================= # =============================================================================
# Sticky / Trap System # Sticky / Trap System
@@ -1065,12 +1014,15 @@ func is_cleansed_cell(pos: Vector2i) -> bool:
func cell_state(pos: Vector2i) -> CellState: func cell_state(pos: Vector2i) -> CellState:
"""Logical state of a playable cell (v2 ground-growth model).""" """Logical state of a playable cell (v2 ground-growth model)."""
var b = get_arena_bounds()
if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max:
return CellState.STICKY
if _is_npc_zone(pos) or _is_boundary(pos): if _is_npc_zone(pos) or _is_boundary(pos):
return CellState.BLOCKED return CellState.BLOCKED
if sticky_cells.has(pos): if is_sticky_cell(pos):
return CellState.STICKY return CellState.STICKY
if cleansed_cells.has(pos): if cleansed_cells.has(pos):
return CellState.CLEANSED return CellState.BLOCKED # Protected from regrowth temporarily
if telegraphed_cells.has(pos): if telegraphed_cells.has(pos):
return CellState.TELEGRAPHED return CellState.TELEGRAPHED
if bubble_cells.has(pos): if bubble_cells.has(pos):
@@ -1091,8 +1043,37 @@ func _tick_cleansed_cells(delta: float) -> void:
for pos in expired: for pos in expired:
cleansed_cells.erase(pos) cleansed_cells.erase(pos)
func get_arena_bounds() -> Dictionary:
match current_phase:
Phase.OPEN_ARENA:
return {"min": 0, "max": 19} # 20x20
Phase.ROUTE_PRESSURE:
return {"min": 1, "max": 18} # 18x18
Phase.SURVIVAL_ENDGAME:
return {"min": 6, "max": 12} # 7x7
return {"min": 0, "max": 19}
func _shrink_arena() -> void:
if not multiplayer.is_server(): return
var b = get_arena_bounds()
var new_sticky = []
for x in range(ARENA_COLUMNS):
for z in range(ARENA_ROWS):
var pos = Vector2i(x, z)
if _is_npc_zone(pos) or _is_boundary(pos):
continue
if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max:
if not sticky_cells.has(pos):
new_sticky.append(pos)
if new_sticky.size() > 0:
if _can_rpc() and multiplayer.is_server():
rpc("sync_growth_apply", new_sticky)
else:
sync_growth_apply(new_sticky)
func _is_boundary(pos: Vector2i) -> bool: func _is_boundary(pos: Vector2i) -> bool:
return pos.x == 0 or pos.x == ARENA_COLUMNS - 1 or pos.y == 0 or pos.y == ARENA_ROWS - 1 return pos.x <= 0 or pos.x >= ARENA_COLUMNS - 1 or pos.y <= 0 or pos.y >= ARENA_ROWS - 1
# ============================================================================= # =============================================================================
# Coverage tracking (v2 target: 70-75%, down from v1's 80%) # Coverage tracking (v2 target: 70-75%, down from v1's 80%)
@@ -1103,12 +1084,15 @@ const COVERAGE_TARGET_MAX: float = 0.75
func playable_cell_count() -> int: func playable_cell_count() -> int:
"""Number of cells that can ever become sticky (interior, minus NPC zone).""" """Number of cells that can ever become sticky (interior, minus NPC zone)."""
var b = get_arena_bounds()
var count := 0 var count := 0
for x in range(ARENA_COLUMNS): for x in range(ARENA_COLUMNS):
for z in range(ARENA_ROWS): for z in range(ARENA_ROWS):
var pos := Vector2i(x, z) var pos := Vector2i(x, z)
if _is_boundary(pos) or _is_npc_zone(pos): if _is_boundary(pos) or _is_npc_zone(pos):
continue continue
if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max:
continue
count += 1 count += 1
return count return count
@@ -1132,6 +1116,9 @@ const FORCED_TRAP_WINDOW: float = 30.0 # final seconds where trapping is allowed
func _is_cell_passable(pos: Vector2i, extra_sticky: Dictionary = {}) -> bool: func _is_cell_passable(pos: Vector2i, extra_sticky: Dictionary = {}) -> bool:
"""Can a player stand on / move through this cell, given a hypothetical sticky set?""" """Can a player stand on / move through this cell, given a hypothetical sticky set?"""
var b = get_arena_bounds()
if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max:
return false
if _is_boundary(pos) or _is_npc_zone(pos): if _is_boundary(pos) or _is_npc_zone(pos):
return false return false
if sticky_cells.has(pos) or extra_sticky.has(pos): if sticky_cells.has(pos) or extra_sticky.has(pos):
@@ -1333,11 +1320,11 @@ func _calculate_bubble_score(pos: Vector2i, player_cells: Array = []) -> float:
return score return score
func _bubble_score_camping(pos: Vector2i) -> float: func _bubble_score_camping(pos: Vector2i) -> float:
"""Reward targeting campers. +40 >5s, +60 >8s, +80 >10s-with-cleanser.""" """Reward targeting campers. +40 >5s, +60 >8s, +80 >10s-with-ghost."""
var t := _camp_time_for_region(_region_of(pos)) var t := _camp_time_for_region(_region_of(pos))
if t > 10.0: if t > 10.0:
# Stronger only if a nearby player actually holds a cleanser. # Stronger only if a nearby player is in ghost mode.
if _any_cleanser_holder_near(pos): if _any_ghost_player_near(pos):
return 80.0 return 80.0
return 60.0 return 60.0
elif t > 8.0: elif t > 8.0:
@@ -1390,22 +1377,25 @@ func _bubble_score_unfair_trap(pos: Vector2i) -> float:
func _bubble_blast_cells(center: Vector2i) -> Array: func _bubble_blast_cells(center: Vector2i) -> Array:
"""The 3x3 (radius 1) sticky cells a bubble at `center` would create, """The 3x3 (radius 1) sticky cells a bubble at `center` would create,
clipped to passable/playable cells.""" clipped to passable/playable cells."""
var b = get_arena_bounds()
var cells: Array = [] var cells: Array = []
for dx in range(-BUBBLE_EXPLOSION_RADIUS, BUBBLE_EXPLOSION_RADIUS + 1): for dx in range(-BUBBLE_EXPLOSION_RADIUS, BUBBLE_EXPLOSION_RADIUS + 1):
for dz in range(-BUBBLE_EXPLOSION_RADIUS, BUBBLE_EXPLOSION_RADIUS + 1): for dz in range(-BUBBLE_EXPLOSION_RADIUS, BUBBLE_EXPLOSION_RADIUS + 1):
var c := center + Vector2i(dx, dz) var c := center + Vector2i(dx, dz)
if _is_boundary(c) or _is_npc_zone(c): if _is_boundary(c) or _is_npc_zone(c):
continue continue
if c.x <= b.min or c.x >= b.max or c.y <= b.min or c.y >= b.max:
continue
cells.append(c) cells.append(c)
return cells return cells
func _any_cleanser_holder_near(pos: Vector2i) -> bool: func _bubble_footprint(center: Vector2i) -> Array:
"""True if a player holding a Cleanser charge is within the camping region.""" return _bubble_blast_cells(center)
func _any_ghost_player_near(pos: Vector2i) -> bool:
"""True if a player in ghost mode is within the camping region."""
for player in get_tree().get_nodes_in_group("Players"): for player in get_tree().get_nodes_in_group("Players"):
var pid = player.get("peer_id") if "peer_id" in player else -1 if not player.get("is_invisible"):
if pid == -1:
continue
if player_cleansers.get(pid, 0) <= 0:
continue continue
if "current_position" in player and player.current_position != null: if "current_position" in player and player.current_position != null:
if _region_of(player.current_position) == _region_of(pos): if _region_of(player.current_position) == _region_of(pos):
@@ -1501,7 +1491,7 @@ func _explode_bubble(center: Vector2i, cells: Array) -> void:
if "current_position" in player and player.current_position != null: if "current_position" in player and player.current_position != null:
if blast.has(player.current_position): if blast.has(player.current_position):
var pid = player.get("peer_id") if "peer_id" in player else -1 var pid = player.get("peer_id") if "peer_id" in player else -1
if pid != -1 and is_cleanser_active(pid): if pid != -1 and player.get("is_invisible"):
continue continue
apply_sticky_slow(player) apply_sticky_slow(player)
@@ -1608,8 +1598,8 @@ func _check_all_players_trapped() -> void:
var pos = player.current_position if player.get("current_position") else Vector2i(-1, -1) var pos = player.current_position if player.get("current_position") else Vector2i(-1, -1)
if is_sticky_cell(pos): if is_sticky_cell(pos):
var pid = player.get("peer_id") if "peer_id" in player else -1 var pid = player.get("peer_id") if "peer_id" in player else -1
if pid != -1 and is_cleanser_active(pid): if pid != -1 and player.get("is_invisible"):
continue # cleansing players are immune to the slow continue # ghost players are immune to the slow
apply_sticky_slow(player) apply_sticky_slow(player)
func apply_sticky_slow(player: Node) -> void: func apply_sticky_slow(player: Node) -> void:
@@ -1641,25 +1631,22 @@ func _trap_player(player: Node) -> void:
NotificationManager.send_message(player, "Stuck in Candy!", NotificationManager.MessageType.WARNING) NotificationManager.send_message(player, "Stuck in Candy!", NotificationManager.MessageType.WARNING)
func clear_sticky_cell(pos: Vector2i) -> void: func clear_sticky_cell(pos: Vector2i) -> void:
"""Used by Cleanser power-up to remove a sticky cell.""" """Remove a sticky cell (used when ghost player walks through)."""
if _can_rpc(): if _can_rpc():
if multiplayer.is_server(): if multiplayer.is_server():
rpc("sync_clear_sticky_cell", pos) rpc("sync_clear_sticky_cell", pos)
else: else:
sync_clear_sticky_cell(pos) # Predictive local clear sync_clear_sticky_cell(pos) # Predictive local clear
rpc("rpc_use_cleanser", pos)
else: else:
sync_clear_sticky_cell(pos) sync_clear_sticky_cell(pos)
@rpc("authority", "call_local", "reliable") @rpc("authority", "call_local", "reliable")
func sync_clear_sticky_cell(pos: Vector2i) -> void: func sync_clear_sticky_cell(pos: Vector2i) -> void:
sticky_cells.erase(pos) sticky_cells.erase(pos)
mark_cleansed(pos) # temporary regrowth protection (v2) mark_cleansed(pos) # temporary regrowth protection
if gridmap: if gridmap:
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), -1) gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), -1)
# Play VFX and SFX
_spawn_cleanser_particles(pos)
if SfxManager: if SfxManager:
SfxManager.play("pick_up_power_tile") SfxManager.play("pick_up_power_tile")
@@ -1667,142 +1654,6 @@ func sync_clear_sticky_cell(pos: Vector2i) -> void:
if main_scene and main_scene.has_method("sync_grid_item"): if main_scene and main_scene.has_method("sync_grid_item"):
main_scene.sync_grid_item(pos.x, 2, pos.y, -1) main_scene.sync_grid_item(pos.x, 2, pos.y, -1)
func _try_use_cleanser() -> void:
"""Local player attempts to activate Cleanser for 5-cell sticky immunity."""
var local_pid = multiplayer.get_unique_id()
var count = player_cleansers.get(local_pid, 0)
if count <= 0:
return
# Block activation during stun
var all_players = get_tree().get_nodes_in_group("Players")
var local_player = null
for p in all_players:
var pid = p.get("peer_id") if "peer_id" in p else p.name.to_int()
if pid == local_pid:
local_player = p
break
if not local_player:
return
if local_player.get("is_frozen") or local_player.get("is_stop_frozen"):
return
# 0.3s activation delay
await get_tree().create_timer(CLEANSER_ACTIVATION_DELAY).timeout
# Re-validate after delay
if not is_instance_valid(local_player):
return
if local_player.get("is_frozen") or local_player.get("is_stop_frozen"):
return
# Consume cleanser from inventory (only if client, host relies on rpc)
if not multiplayer.is_server():
cleanser_active[local_pid] = true
cleanser_cells_left[local_pid] = CLEANSER_MAX_CELLS
player_cleansers[local_pid] = max(0, player_cleansers[local_pid] - 1)
update_cleanser_ui(player_cleansers[local_pid])
NotificationManager.send_message(local_player, "Cleanser Used! (5 charges)", NotificationManager.MessageType.POWERUP)
# Sync to server/clients
if not multiplayer.is_server() and _can_rpc():
rpc("rpc_activate_cleanser", local_pid)
elif multiplayer.is_server():
# Call RPC logic directly for host (it will set active/cells_left/consume)
rpc_activate_cleanser(local_pid)
@rpc("any_peer", "call_local", "reliable")
func deactivate_cleanser(player_id: int) -> void:
"""Deactivate cleanser immunity for a player."""
cleanser_active.erase(player_id)
cleanser_cells_left.erase(player_id)
func is_cleanser_active(player_id: int) -> bool:
"""Check if a player has active cleanser immunity."""
return cleanser_active.has(player_id)
func use_cleanser_cell(player_id: int) -> bool:
"""Use one cleanser cell. Returns true if still active, false if exhausted."""
if not cleanser_active.has(player_id):
return false
cleanser_cells_left[player_id] -= 1
if cleanser_cells_left[player_id] <= 0:
if _can_rpc():
rpc("deactivate_cleanser", player_id)
else:
deactivate_cleanser(player_id)
return false
return true
func notify_movement_stopped(player_id: int, pos: Vector2i) -> void:
"""Called from PlayerMovementManager when a move chain settles.
Previously deactivated cleanser here, but now immunity persists
until charges run out to allow repeated use across safe gaps."""
pass
@rpc("any_peer", "call_local", "reliable")
func rpc_activate_cleanser(pid: int) -> void:
"""RPC for clients to activate cleanser on server."""
if multiplayer.is_server():
# Verify they actually have a cleanser charge (prevents spam/cheats)
if player_cleansers.get(pid, 0) <= 0:
return
# Always apply the state and AoE, since this is the server authority
cleanser_active[pid] = true
cleanser_cells_left[pid] = CLEANSER_MAX_CELLS
player_cleansers[pid] = max(0, player_cleansers[pid] - 1)
if _can_rpc():
rpc("sync_cleanser_count", pid, player_cleansers[pid])
# NEW: Clear 3x3 area around player
var all_players = get_tree().get_nodes_in_group("Players")
var target_player = null
for p in all_players:
var target_pid = p.get("peer_id") if "peer_id" in p else p.name.to_int()
if target_pid == pid:
target_player = p
break
if gridmap and is_instance_valid(target_player):
var map_pos = gridmap.local_to_map(target_player.global_position)
var center_pos = Vector2i(map_pos.x, map_pos.z)
# 3x3 neighborhood
for dx in range(-1, 2):
for dz in range(-1, 2):
var check_pos = center_pos + Vector2i(dx, dz)
if is_sticky_cell(check_pos):
clear_sticky_cell(check_pos)
# Remove slow effect for any player in the cleansed area
for p in all_players:
if is_instance_valid(p) and p.has_method("remove_slow_effect"):
if gridmap:
var p_map_pos = gridmap.local_to_map(p.global_position)
var p_cell_pos = Vector2i(p_map_pos.x, p_map_pos.z)
if abs(p_cell_pos.x - center_pos.x) <= 1 and abs(p_cell_pos.y - center_pos.y) <= 1:
if _can_rpc():
p.rpc("remove_slow_effect")
else:
p.remove_slow_effect()
print("[Cleanser] Server cleared 3x3 area around %s for player %d" % [center_pos, pid])
@rpc("any_peer", "call_local", "reliable")
func rpc_use_cleanser(pos: Vector2i) -> void:
"""RPC for clients to clear a sticky cell via Cleanser."""
if multiplayer.is_server():
clear_sticky_cell(pos)
@rpc("any_peer", "call_local", "reliable")
func rpc_consume_cleanser(pid: int) -> void:
"""RPC for clients to report Cleanser consumption to server."""
if multiplayer.is_server():
player_cleansers[pid] = 0
if _can_rpc():
rpc("sync_cleanser_count", pid, 0)
@rpc("any_peer", "reliable") @rpc("any_peer", "reliable")
func rpc_trigger_slowmo() -> void: func rpc_trigger_slowmo() -> void:
"""RPC for clients to request slow-mo from server.""" """RPC for clients to request slow-mo from server."""
@@ -1889,25 +1740,7 @@ func _setup_hud() -> void:
hud_layer.visible = false hud_layer.visible = false
add_child(hud_layer) add_child(hud_layer)
phase_label = hud_layer.get_node("BottomContainer/VBoxContainer/PhaseLabel") phase_label = hud_layer.get_node("BottomContainer/VBoxContainer/PhaseLabel")
cleanser_icon = hud_layer.get_node("BottomContainer/VBoxContainer/CleanserHBox/CleanserIcon")
cleanser_label = hud_layer.get_node("BottomContainer/VBoxContainer/CleanserHBox/CleanserLabel")
slowmo_label = hud_layer.get_node_or_null("TopContainer/SlowMoLabel") slowmo_label = hud_layer.get_node_or_null("TopContainer/SlowMoLabel")
_generate_cleanser_icon()
func _generate_cleanser_icon() -> void:
var icon_img = Image.create(16, 16, false, Image.FORMAT_RGBA8)
icon_img.fill(Color(0.4, 0.9, 1.0))
icon_img.blend_rect(icon_img, Rect2i(2, 2, 12, 12), Vector2i(1, 1))
for x in range(16):
icon_img.set_pixel(x, 0, Color(0.2, 0.6, 0.7))
icon_img.set_pixel(x, 15, Color(0.2, 0.6, 0.7))
for y in range(16):
icon_img.set_pixel(0, y, Color(0.2, 0.6, 0.7))
icon_img.set_pixel(15, y, Color(0.2, 0.6, 0.7))
for i in range(4, 12):
icon_img.set_pixel(i, 7, Color(1.0, 1.0, 1.0, 0.8))
icon_img.set_pixel(7, i, Color(1.0, 1.0, 1.0, 0.8))
cleanser_icon.texture = ImageTexture.create_from_image(icon_img)
func _update_hud_phase(phase_name: String) -> void: func _update_hud_phase(phase_name: String) -> void:
if phase_label: if phase_label:
@@ -1926,20 +1759,6 @@ func _update_hud_phase(phase_name: String) -> void:
# Animate phase label with bounce effect # Animate phase label with bounce effect
_animate_phase_label() _animate_phase_label()
func update_cleanser_ui(count: int) -> void:
cleanser_count = count
if cleanser_label:
cleanser_label.text = "[E] Cleanser (%d)" % count
# Show/hide icon based on availability
if cleanser_icon:
cleanser_icon.visible = count > 0
if count > 0:
# Pulse animation when cleanser is available
var tween = create_tween()
tween.set_loops(2)
tween.tween_property(cleanser_icon, "modulate", Color(1.5, 1.5, 1.5, 1), 0.3)
tween.tween_property(cleanser_icon, "modulate", Color.WHITE, 0.3)
func _animate_phase_label() -> void: func _animate_phase_label() -> void:
"""Animate phase label with bounce effect.""" """Animate phase label with bounce effect."""
if not phase_label: if not phase_label:
@@ -1964,7 +1783,7 @@ func _animate_phase_label() -> void:
# ============================================================================= # =============================================================================
func _on_goal_count_updated(peer_id: int, count: int) -> void: func _on_goal_count_updated(peer_id: int, count: int) -> void:
"""Called when a player completes a goal cycle. Grant cleanser every 2 missions.""" """Called when a player completes a goal cycle. Grant ghost powerup every 2 missions."""
if not multiplayer.is_server(): if not multiplayer.is_server():
return return
@@ -1973,34 +1792,29 @@ func _on_goal_count_updated(peer_id: int, count: int) -> void:
player_mission_completions[peer_id] = 0 player_mission_completions[peer_id] = 0
player_mission_completions[peer_id] += 1 player_mission_completions[peer_id] += 1
# Grant cleanser every 2 missions # Grant ghost powerup every 2 missions
var completions = player_mission_completions[peer_id] var completions = player_mission_completions[peer_id]
if completions % 2 == 0: if completions % 2 == 0:
if not player_cleansers.has(peer_id): _grant_ghost_powerup(peer_id)
player_cleansers[peer_id] = 0
func _grant_ghost_powerup(peer_id: int) -> void:
# Allow stacking cleanser charges instead of capping at 1 """Grant the ghost (invisible mode) powerup to a player."""
player_cleansers[peer_id] += 1 var all_players = get_tree().get_nodes_in_group("Players")
emit_signal("cleanser_granted", peer_id) for p in all_players:
print("[Gauntlet] Player %d granted Cleanser (Total: %d) (mission %d)" % [peer_id, player_cleansers[peer_id], completions]) var pid = p.get("peer_id") if "peer_id" in p else p.name.to_int()
if pid == peer_id:
# Sync cleanser count to HUD var stm = p.get_node_or_null("SpecialTilesManager")
rpc("sync_cleanser_count", peer_id, player_cleansers.get(peer_id, 0)) if stm and stm.has_method("add_powerup_from_item"):
stm.add_powerup_from_item(14) # 14 = Ghost / INVISIBLE_MODE
emit_signal("ghost_granted", peer_id)
print("[Gauntlet] Player %d granted Ghost powerup (mission %d)" % [peer_id, player_mission_completions[peer_id]])
NotificationManager.send_message(p, "Ghost Power Earned!", NotificationManager.MessageType.POWERUP)
break
func _on_score_updated(peer_id: int, new_score: int) -> void: func _on_score_updated(peer_id: int, new_score: int) -> void:
"""Called when a player's score is updated.""" """Called when a player's score is updated."""
pass # Score sync handled by GoalsCycleManager pass # Score sync handled by GoalsCycleManager
@rpc("authority", "call_local", "reliable")
func sync_cleanser_count(peer_id: int, count: int) -> void:
"""Sync cleanser count to HUD for specific player."""
# Update local player's cleanser UI
var local_pid = multiplayer.get_unique_id()
if peer_id == local_pid:
update_cleanser_ui(count)
# ============================================================================= # =============================================================================
# Utility # Utility
# ============================================================================= # =============================================================================
+31 -45
View File
@@ -155,19 +155,15 @@ func simple_move_to(grid_position: Vector2i) -> bool:
player.knock_tekton() player.knock_tekton()
return false # Don't move into the tile, just knock return false # Don't move into the tile, just knock
# If moving into a sticky cell: slow the player (unless cleanser active, # If moving into a sticky cell: block movement unless player is in ghost
# which clears the cell instead). Sticky no longer hard-traps. # mode (is_invisible), which lets them bypass sticky tiles in gauntlet.
if gm and gm.is_active and gm.is_sticky_cell(grid_position): if gm and gm.is_active and gm.is_sticky_cell(grid_position):
var pid = player.get("peer_id") if "peer_id" in player else -1 if player.get("is_invisible"):
if pid != -1 and gm.is_cleanser_active(pid): # Ghost mode: walk through sticky tile freely
# Cleanser immunity: clear sticky cell, use one cell, don't slow print("[Move] Ghost mode bypassed sticky cell at %s" % grid_position)
gm.clear_sticky_cell(grid_position)
gm.use_cleanser_cell(pid)
print("[Move] Cleanser cleared sticky cell at %s (%d cells left)" % [grid_position, gm.cleanser_cells_left.get(pid, 0)])
else: else:
print("[Move] Player stepping into sticky cell at %s — slowed" % grid_position) print("[Move] Failed: Blocked by Gauntlet Sticky cell at %s" % grid_position)
if player.is_multiplayer_authority() or multiplayer.is_server(): return false
gm.apply_sticky_slow(player)
rotate_towards_target(grid_position) rotate_towards_target(grid_position)
@@ -342,12 +338,9 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
if main_sticky and main_sticky.get("gauntlet_manager"): if main_sticky and main_sticky.get("gauntlet_manager"):
var gm_sticky = main_sticky.gauntlet_manager var gm_sticky = main_sticky.gauntlet_manager
if gm_sticky.is_active and gm_sticky.is_sticky_cell(pushed_to_pos): if gm_sticky.is_active and gm_sticky.is_sticky_cell(pushed_to_pos):
var push_pid = other_player.get("peer_id") if "peer_id" in other_player else -1 if other_player.get("is_invisible"):
if push_pid != -1 and gm_sticky.is_cleanser_active(push_pid): # Ghost mode: pushed player bypasses sticky
# Cleanser immunity: clear sticky cell, use one cell print("[Move] Ghost mode bypassed push-into-sticky at %s" % pushed_to_pos)
gm_sticky.clear_sticky_cell(pushed_to_pos)
gm_sticky.use_cleanser_cell(push_pid)
print("[Move] Cleanser cleared push-into-sticky at %s" % pushed_to_pos)
else: else:
print("[Move] Player pushed into sticky cell at %s — slowed" % pushed_to_pos) print("[Move] Player pushed into sticky cell at %s — slowed" % pushed_to_pos)
if multiplayer.is_server() or other_player.is_multiplayer_authority(): if multiplayer.is_server() or other_player.is_multiplayer_authority():
@@ -365,23 +358,27 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
# Consume all available boost to force a full recharge cycle # Consume all available boost to force a full recharge cycle
player.powerup_manager.consume_boost(100.0) player.powerup_manager.consume_boost(100.0)
# SCORING: 200 Points for successful attack (ONLY in Free Mode) # NEW: Always clear charged strike state to prevent multi-hit spam
if player.is_multiplayer_authority(): if player.get("is_charged_strike"):
var is_sng = LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO) player.set("is_charged_strike", false)
if not is_sng:
var main_score = player.get_tree().get_root().get_node_or_null("Main") # SCORING: 200 Points for successful attack (ONLY in Free Mode)
if main_score: if player.is_multiplayer_authority():
var gcm = main_score.get_node_or_null("GoalsCycleManager") var is_sng = LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO)
if gcm: if not is_sng:
if multiplayer.is_server(): var main_score = player.get_tree().get_root().get_node_or_null("Main")
# Server/Bot: Directly add score to specific player ID if main_score:
gcm.add_score(player.name.to_int(), 200) var gcm = main_score.get_node_or_null("GoalsCycleManager")
else: if gcm:
# Client: Request score add (sender ID used) if multiplayer.is_server():
gcm.rpc("request_add_score", 200) # Server/Bot: Directly add score to specific player ID
NotificationManager.send_message(player, "Successful Attack! +200 Pts", NotificationManager.MessageType.GOAL) gcm.add_score(player.name.to_int(), 200)
else: else:
NotificationManager.send_message(player, "Successful Attack!", NotificationManager.MessageType.GOAL) # Client: Request score add (sender ID used)
gcm.rpc("request_add_score", 200)
NotificationManager.send_message(player, "Successful Attack! +200 Pts", NotificationManager.MessageType.GOAL)
else:
NotificationManager.send_message(player, "Successful Attack!", NotificationManager.MessageType.GOAL)
# 5. Block the attacker from moving into the victim's space to prevent overlapping # 5. Block the attacker from moving into the victim's space to prevent overlapping
return false return false
@@ -407,17 +404,6 @@ func _on_movement_finished():
emit_signal("movement_finished") emit_signal("movement_finished")
else: else:
current_move_direction = Vector2i.ZERO current_move_direction = Vector2i.ZERO
# Gauntlet (#072): a Cleanser ends early once the player rests on a safe
# cell. Gated on gm.is_active so other game modes are never affected.
var gm = null
var main_node = player.get_tree().root.get_node_or_null("Main")
if main_node and main_node.get("gauntlet_manager"):
gm = main_node.gauntlet_manager
if gm and gm.is_active and player.get("current_position") != null:
var mpid = player.get("peer_id") if "peer_id" in player else -1
if mpid != -1 and gm.is_cleanser_active(mpid):
if multiplayer.is_server() or player.is_multiplayer_authority():
gm.notify_movement_stopped(mpid, player.current_position)
emit_signal("movement_finished") emit_signal("movement_finished")
func move_to_clicked_position(grid_position: Vector2i) -> bool: func move_to_clicked_position(grid_position: Vector2i) -> bool:
+5 -3
View File
@@ -547,9 +547,11 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true, only_c
# Spawn ONLY common tiles (7-10) in Stop n Go mode (User Request) # Spawn ONLY common tiles (7-10) in Stop n Go mode (User Request)
item_id = rng.randi_range(7, 10) item_id = rng.randi_range(7, 10)
elif LobbyManager.is_game_mode(GameMode.Mode.GAUNTLET): elif LobbyManager.is_game_mode(GameMode.Mode.GAUNTLET):
# Gauntlet mode: No power-up tile spawns from world. # Gauntlet mode: mostly common tiles, but ghost (14) can spawn too.
# Only common tiles (7-10) spawn; Smack/Cleanser are handled separately. if rng.randf() < 0.85:
item_id = rng.randi_range(7, 10) item_id = rng.randi_range(7, 10)
else:
item_id = 14 # Ghost powerup only
else: else:
# Other modes: 80% Chance for Common Tile (7-10), 20% for PowerUp # Other modes: 80% Chance for Common Tile (7-10), 20% for PowerUp
if rng.randf() < 0.8: if rng.randf() < 0.8:
+4
View File
@@ -70,6 +70,10 @@ func _ready() -> void:
pull_10_btn.pressed.connect(func(): _do_pull(10)) pull_10_btn.pressed.connect(func(): _do_pull(10))
close_result_btn.pressed.connect(func(): result_panel.hide()) close_result_btn.pressed.connect(func(): result_panel.hide())
craft_btn.pressed.connect(_on_open_craft) craft_btn.pressed.connect(_on_open_craft)
if UserProfileManager.profile_updated.connect(_refresh_ui) != OK:
pass
result_panel.hide() result_panel.hide()
_switch_banner("star") _switch_banner("star")
+1 -1
View File
@@ -26,7 +26,7 @@ PROJECT_GODOT = "project.godot"
CHANGELOG_DRAFT = "CHANGELOG_DRAFT.md" CHANGELOG_DRAFT = "CHANGELOG_DRAFT.md"
VERSION_JSON = "assets/data/version.json" VERSION_JSON = "assets/data/version.json"
EXPORT_PRESETS = "export_presets.cfg" EXPORT_PRESETS = "export_presets.cfg"
MANIFEST_URL = "https://raw.githubusercontent.com/adtpdn/tekton-updates/main/latest/patch.pck" MANIFEST_URL = "https://git.klud.top/danchie/tekton/raw/branch/patches/patch.pck"
# ─── Parse command line arguments ───────────────────────────────────────────── # ─── Parse command line arguments ─────────────────────────────────────────────
parser = argparse.ArgumentParser(description="Generate version.json and update version numbers") parser = argparse.ArgumentParser(description="Generate version.json and update version numbers")