Compare commits
89 Commits
c471afaee4
...
v2.4.3
| Author | SHA1 | Date | |
|---|---|---|---|
| bd8b81bbc1 | |||
| a2d008a480 | |||
| 260a448a70 | |||
| e73421c9b1 | |||
| 2dd3020d0b | |||
| fbca048aba | |||
| 4c6c8e1587 | |||
| 939d2b0ac2 | |||
| e845c1be25 | |||
| 1f6bdf4d06 | |||
| 36ae0d479d | |||
| c23c5f77bc | |||
| 1691d8ebc9 | |||
| ab93f047ab | |||
| b15e1153e0 | |||
| e6e4c7b6f6 | |||
| d524f23104 | |||
| 7762a82d18 | |||
| 0786638a98 | |||
| 08a527195f | |||
| 4a3637482c | |||
| 8414f48106 | |||
| 7c6f66e821 | |||
| 2643ef24fa | |||
| c47fadf152 | |||
| 4486e4d3fd | |||
| 6971c27d77 | |||
| 7e6e0f7bef | |||
| 869f670605 | |||
| db061a7946 | |||
| 20cd2d08b8 | |||
| 1652630153 | |||
| 34039db92c | |||
| bf9ae51702 | |||
| 7a02eee277 | |||
| d6daed62b8 | |||
| 0548f54168 | |||
| 3fe8de2e32 | |||
| f40dae5a03 | |||
| ab3ffbbec8 | |||
| da5c319a5b | |||
| 5e5d0c8ecf | |||
| b5e22f3ca5 | |||
| 8abf07a0d4 | |||
| 350ae269f2 | |||
| e8604e2c02 | |||
| c9995f8578 | |||
| 4aa765c502 | |||
| 1d653bb7d0 | |||
| 4fe0378d1c | |||
| fb58e62fd9 | |||
| aa45bb0afd | |||
| d05ebdff05 | |||
| e984c1f8b5 | |||
| 99d38134b8 | |||
| 66bc1658a4 | |||
| 3cb8a606b5 | |||
| 19e7f619ab | |||
| 5ba7de3fd6 | |||
| a89e54783f | |||
| 376be28366 | |||
| 0415875128 | |||
| 03028413ca | |||
| 340ba13a48 | |||
| 04f7fdd2dd | |||
| edce1fa1fa | |||
| 6ac9acea35 | |||
| 9ddb1dd199 | |||
| 74de7f15ef | |||
| d40a242cbc | |||
| 419771c50b | |||
| e7a0717aff | |||
| 754fb0d0a3 | |||
| 2fd8130743 | |||
| 5ad9952750 | |||
| 720882f0dc | |||
| 456ca94786 | |||
| 78f8c5bf5e | |||
| 58b66bcb8f | |||
| 477c7ca7ec | |||
| ecb080e943 | |||
| e539a862d5 | |||
| 82763e4d5a | |||
| c30888b74c | |||
| 858bf08212 | |||
| 64630662ac | |||
| 187b530cbf | |||
| 6da55003e2 | |||
| 598065f255 |
@@ -1,122 +0,0 @@
|
|||||||
name: Build Artifacts
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
platform:
|
|
||||||
description: 'Platform to build'
|
|
||||||
required: true
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- windows
|
|
||||||
- linux
|
|
||||||
- android
|
|
||||||
- all
|
|
||||||
version:
|
|
||||||
description: 'Version tag (optional)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-windows:
|
|
||||||
name: Build Windows
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 45
|
|
||||||
if: github.event.inputs.platform == 'windows' || github.event.inputs.platform == 'all'
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install export templates
|
|
||||||
run: |
|
|
||||||
mkdir -v -p ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
wget -q https://github.com/godotengine/godot-builds/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz
|
|
||||||
unzip -q Godot_v4.6-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
|
|
||||||
- name: Export Windows
|
|
||||||
run: |
|
|
||||||
godot --headless --export-release "Windows Desktop" build/tekton_armageddon_${{ github.event.inputs.version || 'dev' }}_windows.exe
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: tekton-windows-${{ github.event.inputs.version || 'dev' }}
|
|
||||||
path: build/tekton_armageddon_${{ github.event.inputs.version || 'dev' }}_windows.exe
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
build-linux:
|
|
||||||
name: Build Linux
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 45
|
|
||||||
if: github.event.inputs.platform == 'linux' || github.event.inputs.platform == 'all'
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install export templates
|
|
||||||
run: |
|
|
||||||
mkdir -v -p ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
wget -q https://github.com/godotengine/godot-builds/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz
|
|
||||||
unzip -q Godot_v4.6-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
|
|
||||||
- name: Export Linux
|
|
||||||
run: |
|
|
||||||
godot --headless --export-release "Linux/X11" build/tekton_armageddon_${{ github.event.inputs.version || 'dev' }}_linux.x86_64
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: tekton-linux-${{ github.event.inputs.version || 'dev' }}
|
|
||||||
path: build/tekton_armageddon_${{ github.event.inputs.version || 'dev' }}_linux.x86_64
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
build-android:
|
|
||||||
name: Build Android
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 60
|
|
||||||
if: github.event.inputs.platform == 'android' || github.event.inputs.platform == 'all'
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install export templates
|
|
||||||
run: |
|
|
||||||
mkdir -v -p ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
wget -q https://github.com/godotengine/godot-builds/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz
|
|
||||||
unzip -q Godot_v4.6-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
with:
|
|
||||||
api-level: 34
|
|
||||||
ndk-version: r26c
|
|
||||||
cmdline-tools-version: latest
|
|
||||||
|
|
||||||
- name: Export Android
|
|
||||||
run: |
|
|
||||||
godot --headless --export-release "Android" build/tekton_dash_armageddon_${{ github.event.inputs.version || 'dev' }}.apk
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: tekton-android-${{ github.event.inputs.version || 'dev' }}
|
|
||||||
path: build/tekton_dash_armageddon_${{ github.event.inputs.version || 'dev' }}.apk
|
|
||||||
retention-days: 30
|
|
||||||
+127
-144
@@ -1,171 +1,154 @@
|
|||||||
name: CI
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, experimental, patch-release]
|
tags:
|
||||||
pull_request:
|
- 'v*'
|
||||||
branches: [main, experimental, patch-release]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
release:
|
||||||
name: Run Tests
|
name: Build & Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
|
||||||
|
TAG_NAME: ${{ github.ref_name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Install tools
|
||||||
uses: actions/checkout@v4
|
run: apt-get update -qq && apt-get install -y -qq curl unzip zip
|
||||||
|
|
||||||
- name: Setup Godot
|
- name: Checkout Code
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
run: |
|
||||||
# Install Godot headless dependencies
|
git config --global credential.helper store
|
||||||
sudo apt-get update
|
echo "http://god:${{ secrets.TEKTON_RELEASE_TOKEN }}@52.74.133.55:3000" > ~/.git-credentials
|
||||||
sudo apt-get install -y libvulkan1 libx11-6 libxcursor1 libxi6 libxrandr2 libxinerama1 libxi6 libxxf86vm1 libgl1
|
git clone http://52.74.133.55:3000/danchie/tekton.git .
|
||||||
|
git checkout $TAG_NAME
|
||||||
|
|
||||||
- name: Run GUT tests
|
- name: Setup Godot (Cached)
|
||||||
run: |
|
run: |
|
||||||
godot --headless -d --path . -s addons/gut/gut_cmdln.gd
|
apt-get update -qq && apt-get install -y -qq unzip curl
|
||||||
|
if [ ! -f /cache/godot_4.7 ]; then
|
||||||
export-windows:
|
echo "Downloading Godot 4.7..."
|
||||||
name: Export Windows
|
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"
|
||||||
runs-on: ubuntu-latest
|
unzip -q -o /tmp/godot.zip -d /cache/
|
||||||
timeout-minutes: 45
|
mv /cache/Godot_v4.7-stable_linux.x86_64 /cache/godot_4.7
|
||||||
needs: test
|
fi
|
||||||
steps:
|
cp /cache/godot_4.7 /usr/local/bin/godot
|
||||||
- name: Checkout repository
|
chmod +x /usr/local/bin/godot
|
||||||
uses: actions/checkout@v4
|
mkdir -p ~/.local/share/godot/export_templates/4.7.stable
|
||||||
|
if [ ! -f /cache/Godot_v4.7-stable_export_templates.tpz ]; then
|
||||||
- name: Setup Godot
|
echo "Downloading templates..."
|
||||||
uses: firebelley/godot-action@v3
|
curl -sL -o /cache/Godot_v4.7-stable_export_templates.tpz \
|
||||||
with:
|
"https://github.com/godotengine/godot-builds/releases/download/4.7-stable/Godot_v4.7-stable_export_templates.tpz"
|
||||||
godot-version: '4.6'
|
fi
|
||||||
engine-type: standard
|
cd ~/.local/share/godot/export_templates/4.7.stable
|
||||||
|
unzip -q -o /cache/Godot_v4.7-stable_export_templates.tpz
|
||||||
- name: Install export templates
|
mv templates/* .
|
||||||
run: |
|
rm -rf templates
|
||||||
mkdir -v -p ~/.local/share/godot/export_templates/4.6.stable
|
cd $GITHUB_WORKSPACE
|
||||||
wget -q https://github.com/godotengine/godot-builds/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz
|
mkdir -p build
|
||||||
unzip -q Godot_v4.6-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
|
|
||||||
- name: Export Windows
|
- name: Export Windows
|
||||||
run: |
|
run: |
|
||||||
godot --headless --export-release "Windows Desktop" build/tekton_armageddon_windows.exe
|
mkdir -p build/windows
|
||||||
|
cp addons/godotsteam/libgodotsteam* build/windows/ 2>/dev/null || true
|
||||||
- name: Upload Windows artifact
|
godot --headless --export-release "Windows Desktop" build/windows/tekton_armageddon_windows.exe || true
|
||||||
uses: actions/upload-artifact@v4
|
cd build/windows && zip -r ../tekton_armageddon_windows_${TAG_NAME}.zip .
|
||||||
with:
|
|
||||||
name: tekton-windows
|
|
||||||
path: build/tekton_armageddon_windows.exe
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
export-linux:
|
|
||||||
name: Export Linux
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 45
|
|
||||||
needs: test
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install export templates
|
|
||||||
run: |
|
|
||||||
mkdir -v -p ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
wget -q https://github.com/godotengine/godot-builds/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz
|
|
||||||
unzip -q Godot_v4.6-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
|
|
||||||
- name: Export Linux
|
- name: Export Linux
|
||||||
run: |
|
run: |
|
||||||
godot --headless --export-release "Linux/X11" build/tekton_armageddon_linux.x86_64
|
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: Upload Linux artifact
|
- name: Export macOS
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: tekton-linux
|
|
||||||
path: build/tekton_armageddon_linux.x86_64
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
export-android:
|
|
||||||
name: Export Android
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 60
|
|
||||||
needs: test
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install export templates
|
|
||||||
run: |
|
run: |
|
||||||
mkdir -v -p ~/.local/share/godot/export_templates/4.6.stable
|
mkdir -p build/macos
|
||||||
wget -q https://github.com/godotengine/godot-builds/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz
|
godot --headless --export-release "macOS" build/macos/tekton_armageddon_macos.zip 2>&1 | tail -5 || true
|
||||||
unzip -q Godot_v4.6-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.6.stable
|
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: Setup Android SDK
|
- name: Extract changelog
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
with:
|
|
||||||
api-level: 34
|
|
||||||
ndk-version: r26c
|
|
||||||
cmdline-tools-version: latest
|
|
||||||
|
|
||||||
- name: Export Android
|
|
||||||
run: |
|
run: |
|
||||||
godot --headless --export-release "Android" build/tekton_dash_armageddon.apk
|
# 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: Upload Android artifact
|
- name: Create Gitea Release
|
||||||
uses: actions/upload-artifact@v4
|
run: |
|
||||||
with:
|
set -e
|
||||||
name: tekton-android
|
API="http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases"
|
||||||
path: build/tekton_dash_armageddon.apk
|
TAG="$TAG_NAME"
|
||||||
retention-days: 7
|
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
|
||||||
|
|
||||||
release:
|
- name: Upload Windows asset
|
||||||
name: Create Release
|
run: |
|
||||||
runs-on: ubuntu-latest
|
RELEASE_ID=$(cat /tmp/release_id.txt)
|
||||||
timeout-minutes: 15
|
curl -s -X POST \
|
||||||
needs: [export-windows, export-linux, export-android]
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
-H "Content-Type: multipart/form-data" \
|
||||||
permissions:
|
-F "attachment=@build/tekton_armageddon_windows_${TAG_NAME}.zip" \
|
||||||
contents: write
|
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
||||||
steps:
|
echo "Windows uploaded"
|
||||||
- name: Download Windows artifact
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: tekton-windows
|
|
||||||
path: artifacts/windows
|
|
||||||
|
|
||||||
- name: Download Linux artifact
|
- name: Upload Linux asset
|
||||||
uses: actions/download-artifact@v4
|
run: |
|
||||||
with:
|
RELEASE_ID=$(cat /tmp/release_id.txt)
|
||||||
name: tekton-linux
|
curl -s -X POST \
|
||||||
path: artifacts/linux
|
-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: Download Android artifact
|
- name: Upload macOS asset
|
||||||
uses: actions/download-artifact@v4
|
run: |
|
||||||
with:
|
RELEASE_ID=$(cat /tmp/release_id.txt)
|
||||||
name: tekton-android
|
if [ -f "build/tekton_armageddon_macos_${TAG_NAME}.zip" ]; then
|
||||||
path: artifacts/android
|
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: Create Release
|
- name: Publish release
|
||||||
uses: softprops/action-gh-release@v2
|
run: |
|
||||||
with:
|
RELEASE_ID=$(cat /tmp/release_id.txt)
|
||||||
files: |
|
curl -s -X PATCH \
|
||||||
artifacts/windows/tekton_armageddon_windows.exe
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
artifacts/linux/tekton_armageddon_linux.x86_64
|
-H "Content-Type: application/json" \
|
||||||
artifacts/android/tekton_dash_armageddon.apk
|
-d '{"draft":false}' \
|
||||||
generate_release_notes: true
|
"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"
|
||||||
|
|||||||
@@ -13,71 +13,56 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-patch:
|
build-and-deploy:
|
||||||
name: Build Patch
|
name: Build & Deploy Patch
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 45
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository (shallow)
|
||||||
uses: actions/checkout@v4
|
env:
|
||||||
with:
|
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install export templates
|
|
||||||
run: |
|
run: |
|
||||||
mkdir -v -p ~/.local/share/godot/export_templates/4.6.stable
|
git clone --depth 1 http://god:$GITEA_TOKEN@52.74.133.55:3000/danchie/tekton.git .
|
||||||
wget -q https://github.com/godotengine/godot-builds/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz
|
git config user.name "god"
|
||||||
unzip -q Godot_v4.6-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.6.stable
|
git config user.email "god@noreply.git.klud.top"
|
||||||
|
|
||||||
- name: Run patch build script
|
- name: Setup Godot (Cached)
|
||||||
run: |
|
run: |
|
||||||
python3 patch_version.py ${{ github.event.inputs.version }} "${{ github.event.inputs.notes }}"
|
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: Export Windows patch
|
- name: Generate version.json & bump version
|
||||||
|
env:
|
||||||
|
PATCH_VERSION: ${{ github.event.inputs.version }}
|
||||||
|
PATCH_NOTES: ${{ github.event.inputs.notes }}
|
||||||
run: |
|
run: |
|
||||||
godot --headless --export-release "Windows Desktop" build/tekton_armageddon_patch_windows.exe
|
python3 tools/generate_version_json.py --skip-changelog
|
||||||
|
|
||||||
- name: Export Linux patch
|
- name: Export patch PCK
|
||||||
run: |
|
run: |
|
||||||
godot --headless --export-release "Linux/X11" build/tekton_armageddon_patch_linux.x86_64
|
godot --headless --export-pack "Windows Desktop" build/patch.pck 2>&1 | tail -5
|
||||||
|
|
||||||
- name: Upload patch artifacts
|
- name: Push to patches branch
|
||||||
uses: actions/upload-artifact@v4
|
env:
|
||||||
with:
|
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
|
||||||
name: tekton-patch-${{ github.event.inputs.version }}
|
run: |
|
||||||
path: |
|
mkdir -p patch-deploy
|
||||||
build/tekton_armageddon_patch_windows.exe
|
cp build/patch.pck patch-deploy/
|
||||||
build/tekton_armageddon_patch_linux.x86_64
|
cp assets/data/version.json patch-deploy/
|
||||||
retention-days: 30
|
cd patch-deploy
|
||||||
|
git init
|
||||||
deploy-patch:
|
git config user.name "god"
|
||||||
name: Deploy Patch
|
git config user.email "god@noreply.git.klud.top"
|
||||||
runs-on: ubuntu-latest
|
git remote add origin http://god:$GITEA_TOKEN@52.74.133.55:3000/danchie/tekton.git
|
||||||
timeout-minutes: 15
|
git checkout -b patches
|
||||||
needs: build-patch
|
git add .
|
||||||
permissions:
|
git commit -m "patch ${{ github.event.inputs.version }}"
|
||||||
contents: write
|
git push -f origin patches
|
||||||
steps:
|
|
||||||
- name: Download patch artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: tekton-patch-${{ github.event.inputs.version }}
|
|
||||||
path: artifacts/patch
|
|
||||||
|
|
||||||
- name: Create patch release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
tag_name: patch-${{ github.event.inputs.version }}
|
|
||||||
name: "Patch ${{ github.event.inputs.version }}"
|
|
||||||
body: |
|
|
||||||
${{ github.event.inputs.notes }}
|
|
||||||
files: |
|
|
||||||
artifacts/patch/tekton_armageddon_patch_windows.exe
|
|
||||||
artifacts/patch/tekton_armageddon_patch_linux.x86_64
|
|
||||||
prerelease: true
|
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
name: Test Suite
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, experimental, patch-release]
|
|
||||||
pull_request:
|
|
||||||
branches: [main, experimental, patch-release]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
unit-tests:
|
|
||||||
name: Unit Tests (GUT)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libvulkan1 libx11-6 libxcursor1 libxi6 libxrandr2 libxinerama1 libxi6 libxxf86vm1 libgl1
|
|
||||||
|
|
||||||
- name: Run GUT tests
|
|
||||||
run: |
|
|
||||||
godot --headless -d --path . -s addons/gut/gut_cmdln.gd
|
|
||||||
|
|
||||||
integration-tests:
|
|
||||||
name: Integration Tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 45
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Install export templates
|
|
||||||
run: |
|
|
||||||
mkdir -v -p ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
wget -q https://github.com/godotengine/godot-builds/releases/download/4.6-stable/Godot_v4.6-stable_export_templates.tpz
|
|
||||||
unzip -q Godot_v4.6-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.6.stable
|
|
||||||
|
|
||||||
- name: Export headless server for integration tests
|
|
||||||
run: |
|
|
||||||
godot --headless --export-release "Linux/X11" build/tekton_server.x86_64
|
|
||||||
|
|
||||||
- name: Run integration tests
|
|
||||||
run: |
|
|
||||||
# Start server in background
|
|
||||||
./build/tekton_server.x86_64 --headless --port 7777 &
|
|
||||||
SERVER_PID=$!
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
# Run client tests against server
|
|
||||||
godot --headless -d --path . -s addons/gut/gut_cmdln.gd --select=test_integration
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
kill $SERVER_PID
|
|
||||||
|
|
||||||
lint:
|
|
||||||
name: Code Style Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: firebelley/godot-action@v3
|
|
||||||
with:
|
|
||||||
godot-version: '4.6'
|
|
||||||
engine-type: standard
|
|
||||||
|
|
||||||
- name: Check GDScript formatting
|
|
||||||
run: |
|
|
||||||
godot --headless --check-only -s scripts/lint.gd || true
|
|
||||||
# Note: GDScript doesn't have a built-in formatter; this checks syntax only
|
|
||||||
|
|
||||||
security-scan:
|
|
||||||
name: Security Scan
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
|
||||||
uses: aquasecurity/trivy-action@master
|
|
||||||
with:
|
|
||||||
scan-type: 'fs'
|
|
||||||
scan-ref: '.'
|
|
||||||
format: 'sarif'
|
|
||||||
output: 'trivy-results.sarif'
|
|
||||||
|
|
||||||
- name: Upload Trivy results to GitHub Security
|
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
|
||||||
with:
|
|
||||||
sarif_file: 'trivy-results.sarif'
|
|
||||||
+28
-1
@@ -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.
|
||||||
|
|||||||
+34
-31
@@ -1,43 +1,46 @@
|
|||||||
# Cloning from any tailnet machine
|
# Clone Tekton from Gitea
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
- The remote machine must be on your tailnet (registered + can ping `100.85.x.x`)
|
|
||||||
- You must have an account on the Gitea instance (`http://git.klud.top`)
|
|
||||||
- You must have an SSH key on the remote machine (`ssh-keygen` if you don't)
|
|
||||||
|
|
||||||
## Step 1: Add your SSH key to Gitea
|
- You have a Gitea account at `https://git.klud.top`.
|
||||||
1. Open `http://git.klud.top/user/settings/keys` in browser
|
- For SSH clone: your public SSH key is added at `https://git.klud.top/user/settings/keys`.
|
||||||
2. Paste the content of `~/.ssh/id_ed25519.pub` (or `id_rsa.pub`)
|
|
||||||
3. Click "Add Key"
|
## SSH clone (preferred)
|
||||||
|
|
||||||
|
Test SSH:
|
||||||
|
|
||||||
## Step 2: Test SSH reachability
|
|
||||||
```bash
|
```bash
|
||||||
ssh -T git@thunderobot -p 222
|
ssh -T git@ssh.git.klud.top
|
||||||
# Expected output: "Hello from Gitea!\n\nPlease come over!"
|
|
||||||
# If asked about fingerprint, type "yes" and press Enter
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 3: Clone a repo
|
Expected output:
|
||||||
```bash
|
|
||||||
# Using tea CLI
|
|
||||||
tea repos clone --git-protocol ssh danchie/tekton
|
|
||||||
|
|
||||||
# Or plain git
|
```text
|
||||||
git clone git@thunderobot:222/danchie/tekton.git
|
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
|
## Troubleshooting
|
||||||
- `ssh: connect to host thunderobot port 222: Connection refused`
|
|
||||||
→ Ensure the remote machine has `tailscale ping thunderobot` resolving to `100.93.226.13`
|
|
||||||
→ Ensure the host's port `222` is reachable from the remote (it's bound to `0.0.0.0:222` in Docker)
|
|
||||||
|
|
||||||
- `Permission denied (publickey)`
|
- **`Permission denied (publickey)`** — Your key was not added in Gitea, or your SSH agent has not loaded it. Run `ssh-add ~/.ssh/id_ed25519`.
|
||||||
→ Your SSH key wasn't added via Gitea web UI
|
- **`Host key verification failed`** — Run `ssh -T git@ssh.git.klud.top` interactively once and accept the fingerprint.
|
||||||
→ Run `ssh-add ~/.ssh/id_ed25519` if you use an agent
|
- **`Could not resolve hostname ssh.git.klud.top`** — DNS cache stale. Wait a few minutes or flush DNS.
|
||||||
|
|
||||||
- `Host key verification failed`
|
|
||||||
→ Run `ssh -T git@thunderobot -p 222` interactively once to accept the fingerprint
|
|
||||||
|
|
||||||
## HTTPS is disabled
|
|
||||||
`DISABLE_HTTP_GIT = true` is set on the server. All clones MUST use SSH.
|
|
||||||
If you need to fetch via HTTPS for CI or other non-ssh environments, this must be remapped.
|
|
||||||
|
|||||||
@@ -4,22 +4,34 @@
|
|||||||
>
|
>
|
||||||
> 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).
|
> 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).
|
||||||
|
|
||||||
## SSH setup for cloning
|
## Clone
|
||||||
|
|
||||||
All clones **must use SSH** over Tailscale. HTTPS is disabled on this instance.
|
SSH is preferred:
|
||||||
|
|
||||||
Guides by operating system:
|
```bash
|
||||||
|
git clone git@ssh.git.klud.top:danchie/tekton.git
|
||||||
|
```
|
||||||
|
|
||||||
|
HTTPS also works:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.klud.top/danchie/tekton.git
|
||||||
|
```
|
||||||
|
|
||||||
|
First-time SSH setup:
|
||||||
|
|
||||||
- [Linux](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-Linux)
|
- [Linux](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-Linux)
|
||||||
- [macOS](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-macOS)
|
- [macOS](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-macOS)
|
||||||
- [Windows](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-Windows)
|
- [Windows](https://git.klud.top/danchie/tekton/wiki/SSH-Setup-Windows)
|
||||||
|
|
||||||
Quick verification after setup:
|
SSH test:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -T git@thunderobot -p 222
|
ssh -T git@ssh.git.klud.top
|
||||||
```
|
```
|
||||||
|
|
||||||
Once verified, clone:
|
Expected output:
|
||||||
```bash
|
|
||||||
git clone git@thunderobot:222/danchie/tekton.git
|
```text
|
||||||
|
Hi there, <yourname>! You've successfully authenticated with the key named <key-title>, but Gitea does not provide shell access.
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 %s — burning Cleanser" % [actor.name, here])
|
print("[BotController] %s stepped onto sticky at %s — activating Ghost" % [actor.name, here])
|
||||||
return _try_activate_cleanser()
|
return _try_activate_ghost()
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
|
|
||||||
# Allow stacking cleanser charges instead of capping at 1
|
func _grant_ghost_powerup(peer_id: int) -> void:
|
||||||
player_cleansers[peer_id] += 1
|
"""Grant the ghost (invisible mode) powerup to a player."""
|
||||||
emit_signal("cleanser_granted", peer_id)
|
var all_players = get_tree().get_nodes_in_group("Players")
|
||||||
print("[Gauntlet] Player %d granted Cleanser (Total: %d) (mission %d)" % [peer_id, player_cleansers[peer_id], completions])
|
for p in all_players:
|
||||||
|
var pid = p.get("peer_id") if "peer_id" in p else p.name.to_int()
|
||||||
# Sync cleanser count to HUD
|
if pid == peer_id:
|
||||||
rpc("sync_cleanser_count", peer_id, player_cleansers.get(peer_id, 0))
|
var stm = p.get_node_or_null("SpecialTilesManager")
|
||||||
|
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
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user