feat: implement Candy Cannon mechanics, CI/CD pipelines, and version 2.3.7 updates
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
description: This document tells AI agents how to work on Tekton Dash tasks end-to-end.
|
||||
---
|
||||
|
||||
# SKILLS.md — AI Agent Workflow Guide for Tekton Dash
|
||||
# AI Agent Workflow Guide for Tekton Dash
|
||||
|
||||
This document tells AI agents how to work on Tekton Dash tasks end-to-end.
|
||||
|
||||
@@ -12,7 +12,7 @@ This document tells AI agents how to work on Tekton Dash tasks end-to-end.
|
||||
|
||||
All tasks live on the **"TektonDash - Armageddon PR Tasks"** Notion board.
|
||||
|
||||
https://www.notion.so/danchiego-game/36433be43b29800c8422ed5bdd65671b?v=36633be43b29803891cd000c6f6e5c5f
|
||||
https://www.notion.so/danchiego-game/36433be43b29800c8422ed5bdd65671b?v=36433be43b2980de8635000c0a910a0d
|
||||
|
||||
### Finding Tasks
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Pending Notion Tasks for core_coder
|
||||
|
||||
## Instructions
|
||||
Run these commands as `core_coder` profile to complete the 5 "In Progress" tasks:
|
||||
|
||||
```bash
|
||||
# Switch to core_coder profile
|
||||
hermes profile use core_coder
|
||||
|
||||
# Navigate to project
|
||||
cd /home/beng/Godot/Projects/tekton-enet
|
||||
|
||||
# Start interactive session with task context
|
||||
hermes chat -z "Complete the 5 'In Progress' tasks from Notion. Start with P0 task [Gauntlet] #4 Cannon Timer & Basic Volley (ID: 36833be4-3b29-812b-b25e-d087446c57f6). Follow TASKS.md procedure for each task."
|
||||
```
|
||||
|
||||
## Task List (Priority Order)
|
||||
|
||||
### 1. [P0] [Gauntlet] #4 Cannon Timer & Basic Volley
|
||||
- **Notion ID:** 36833be4-3b29-812b-b25e-d087446c57f6
|
||||
- **Type:** CORE
|
||||
- **Description:** Create candy_cannon_controller.gd. Implement 5-second interval cannon timer in GauntletManager._process(). Fire 5 shots per volley at random valid cells (1×1 only initially). Create candy_cannon.tscn (3×3 static NPC scene, is_static_turret=true pattern). Reuse tekton.gd spawn_projectile_rpc() arc-tween pattern for projectile visuals. Sync via rpc().
|
||||
|
||||
### 2. [P1] [036] Mode Config Completeness
|
||||
- **Notion ID:** 36433be4-3b29-8129-bbc9-c3f8437559a8
|
||||
- **Type:** CORE
|
||||
|
||||
### 3. [P1] [024] Set up GitHub Actions CI/CD Workflow
|
||||
- **Notion ID:** 36633be4-3b29-8179-87bf-ec462fb6629f
|
||||
- **Type:** DEVOPS
|
||||
|
||||
### 4. [P1] [032] Set up Artifact Storage & Versioning
|
||||
- **Notion ID:** 36633be4-3b29-812b-9537-f44fbf89c3f9
|
||||
- **Type:** DEVOPS
|
||||
|
||||
### 5. [P1] [031] Configure Platform-Specific Export Presets
|
||||
- **Notion ID:** 36633be4-3b29-8147-8180-eebc89a08dc4
|
||||
- **Type:** DEVOPS
|
||||
|
||||
## Workflow Per Task
|
||||
|
||||
1. Read task details: `mcp_notion_API_retrieve_a_page(page_id="...")`
|
||||
2. Implement changes following TASKS.md
|
||||
3. Write unit test in `tests/test_<feature>.gd`
|
||||
4. Run tests: `./run_tests.cmd` (Windows) or equivalent
|
||||
5. Update CHANGELOG_DRAFT.md (append to existing version block if changes exist)
|
||||
6. Mark Done: `mcp_notion_API_patch_page(page_id="...", properties={"Status": {"select": {"name": "Done"}}, "Acceptance": {"checkbox": true}})`
|
||||
7. Commit changes
|
||||
|
||||
## Notes
|
||||
- Project path: /home/beng/Godot/Projects/tekton-enet
|
||||
- Follow GUT_SETUP_SKILLS.md for testing
|
||||
- Version 2.3.7 already has uncommitted changes — append changelog entries, don't bump version
|
||||
@@ -0,0 +1,138 @@
|
||||
name: Build and Export
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to build (e.g., 2.3.8)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@v1
|
||||
with:
|
||||
version: '4.3.0'
|
||||
use-dotnet: false
|
||||
|
||||
- name: Setup Export Templates
|
||||
run: |
|
||||
mkdir -p ~/.local/share/godot/export_templates/4.3.0.stable
|
||||
wget https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_export_templates.tpz
|
||||
unzip Godot_v4.3-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.3.0.stable
|
||||
|
||||
- name: Export Windows Build
|
||||
run: |
|
||||
mkdir -p build
|
||||
godot --headless --export-release "Windows Desktop" build/tekton_armageddon_windows.exe
|
||||
|
||||
- name: Upload Windows Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-build
|
||||
path: build/tekton_armageddon_windows.exe
|
||||
retention-days: 30
|
||||
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@v1
|
||||
with:
|
||||
version: '4.3.0'
|
||||
use-dotnet: false
|
||||
|
||||
- name: Setup Export Templates
|
||||
run: |
|
||||
mkdir -p ~/.local/share/godot/export_templates/4.3.0.stable
|
||||
wget https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_export_templates.tpz
|
||||
unzip Godot_v4.3-stable_export_templates.tpz -d ~/.local/share/godot/export_templates/4.3.0.stable
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Export Android Build
|
||||
run: |
|
||||
mkdir -p build
|
||||
godot --headless --export-release "Android" build/tekton_armageddon_android.apk
|
||||
|
||||
- name: Upload Android Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: android-build
|
||||
path: build/tekton_armageddon_android.apk
|
||||
retention-days: 30
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@v1
|
||||
with:
|
||||
version: '4.3.0'
|
||||
use-dotnet: false
|
||||
|
||||
- name: Setup Export Templates
|
||||
run: |
|
||||
mkdir -p ~/Library/Application\ Support/Godot/export_templates/4.3.0.stable
|
||||
wget https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_export_templates.tpz
|
||||
unzip Godot_v4.3-stable_export_templates.tpz -d ~/Library/Application\ Support/Godot/export_templates/4.3.0.stable
|
||||
|
||||
- name: Export macOS Build
|
||||
run: |
|
||||
mkdir -p build
|
||||
godot --headless --export-release "macOS" build/tekton_armageddon_macos.zip
|
||||
|
||||
- name: Upload macOS Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-build
|
||||
path: build/tekton_armageddon_macos.zip
|
||||
retention-days: 30
|
||||
|
||||
create-release:
|
||||
needs: [build-windows, build-android, build-macos]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download All Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
windows-build/tekton_armageddon_windows.exe
|
||||
android-build/tekton_armageddon_android.apk
|
||||
macos-build/tekton_armageddon_macos.zip
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,163 @@
|
||||
name: Build Platform Artifacts
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to build (e.g., 2.3.8)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- name: Windows
|
||||
preset: "Windows Desktop"
|
||||
extension: exe
|
||||
- name: Linux
|
||||
preset: "Linux/X11"
|
||||
extension: x86_64
|
||||
- name: Android
|
||||
preset: "Android"
|
||||
extension: apk
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@v1
|
||||
with:
|
||||
version: '4.3.0'
|
||||
use-dotnet: false
|
||||
|
||||
- name: Setup Android SDK (Android only)
|
||||
if: matrix.platform.name == 'Android'
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Extract Version
|
||||
id: version
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
VERSION="${{ inputs.version }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Building version: $VERSION"
|
||||
|
||||
- name: Create Build Directory
|
||||
run: mkdir -p build
|
||||
|
||||
- name: Export Game
|
||||
run: |
|
||||
godot --headless --export-release "${{ matrix.platform.preset }}" \
|
||||
"build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.${{ matrix.platform.extension }}"
|
||||
|
||||
- name: Generate Checksums
|
||||
run: |
|
||||
cd build
|
||||
sha256sum tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.${{ matrix.platform.extension }} \
|
||||
> tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.sha256
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tekton-${{ matrix.platform.name }}-v${{ steps.version.outputs.version }}
|
||||
path: |
|
||||
build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.${{ matrix.platform.extension }}
|
||||
build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.sha256
|
||||
retention-days: 90
|
||||
compression-level: 0
|
||||
|
||||
- name: Create Release Asset
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.${{ matrix.platform.extension }}
|
||||
build/tekton_armageddon_${{ matrix.platform.name }}_v${{ steps.version.outputs.version }}.sha256
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-patch:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@v1
|
||||
with:
|
||||
version: '4.3.0'
|
||||
use-dotnet: false
|
||||
|
||||
- name: Extract Version
|
||||
id: version
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
VERSION="${{ inputs.version }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build Patch PCK
|
||||
run: godot --headless -s tools/build_patch.gd
|
||||
|
||||
- name: Generate Patch Checksum
|
||||
run: |
|
||||
sha256sum patch.pck > patch.pck.sha256
|
||||
|
||||
- name: Upload Patch Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tekton-patch-v${{ steps.version.outputs.version }}
|
||||
path: |
|
||||
patch.pck
|
||||
patch.pck.sha256
|
||||
retention-days: 90
|
||||
|
||||
- name: Push to Updates Repository
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: dmnemec/copy_file_to_another_repo_action@main
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }}
|
||||
with:
|
||||
source_file: 'patch.pck'
|
||||
destination_repo: '${{ github.actor }}/tekton-updates'
|
||||
destination_folder: 'v${{ steps.version.outputs.version }}'
|
||||
user_email: 'action@github.com'
|
||||
user_name: 'PatchBot'
|
||||
commit_message: '[AUTO] Release v${{ steps.version.outputs.version }} patch'
|
||||
|
||||
- name: Push Checksum to Updates Repository
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: dmnemec/copy_file_to_another_repo_action@main
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }}
|
||||
with:
|
||||
source_file: 'patch.pck.sha256'
|
||||
destination_repo: '${{ github.actor }}/tekton-updates'
|
||||
destination_folder: 'v${{ steps.version.outputs.version }}'
|
||||
user_email: 'action@github.com'
|
||||
user_name: 'PatchBot'
|
||||
commit_message: '[AUTO] Release v${{ steps.version.outputs.version }} checksum'
|
||||
@@ -54,7 +54,32 @@ jobs:
|
||||
- name: Run Build Patch Script
|
||||
run: godot --headless -s tools/build_patch.gd
|
||||
|
||||
# ── 5. Push patch.pck to public repo ─────────────────────────────────
|
||||
# ── 5. Generate checksums ─────────────────────────────────────────────────
|
||||
- name: Generate Checksums
|
||||
run: |
|
||||
sha256sum patch.pck > patch.pck.sha256
|
||||
sha256sum assets/data/version.json > version.json.sha256
|
||||
|
||||
# ── 6. Upload artifacts to GitHub ─────────────────────────────────────────
|
||||
- name: Upload Patch Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: patch-pck-${{ github.sha }}
|
||||
path: |
|
||||
patch.pck
|
||||
patch.pck.sha256
|
||||
retention-days: 90
|
||||
|
||||
- name: Upload Version Manifest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: version-manifest-${{ github.sha }}
|
||||
path: |
|
||||
assets/data/version.json
|
||||
version.json.sha256
|
||||
retention-days: 90
|
||||
|
||||
# ── 7. Push patch.pck to public repo ─────────────────────────────────────
|
||||
- name: Push patch.pck to Public Repository
|
||||
uses: dmnemec/copy_file_to_another_repo_action@main
|
||||
env:
|
||||
@@ -67,7 +92,19 @@ jobs:
|
||||
user_name: 'PatchBot'
|
||||
commit_message: '[AUTO] Pushed new patch.pck'
|
||||
|
||||
# ── 6. Push version.json to public repo ──────────────────────────────
|
||||
- name: Push patch checksum to Public Repository
|
||||
uses: dmnemec/copy_file_to_another_repo_action@main
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }}
|
||||
with:
|
||||
source_file: 'patch.pck.sha256'
|
||||
destination_repo: '${{ github.actor }}/tekton-updates'
|
||||
destination_folder: 'latest'
|
||||
user_email: 'action@github.com'
|
||||
user_name: 'PatchBot'
|
||||
commit_message: '[AUTO] Pushed patch checksum'
|
||||
|
||||
# ── 8. Push version.json to public repo ──────────────────────────────────
|
||||
- name: Push version.json to Public Repository
|
||||
uses: dmnemec/copy_file_to_another_repo_action@main
|
||||
env:
|
||||
@@ -79,3 +116,15 @@ jobs:
|
||||
user_email: 'action@github.com'
|
||||
user_name: 'PatchBot'
|
||||
commit_message: '[AUTO] Pushed new version.json'
|
||||
|
||||
- name: Push version checksum to Public Repository
|
||||
uses: dmnemec/copy_file_to_another_repo_action@main
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }}
|
||||
with:
|
||||
source_file: 'version.json.sha256'
|
||||
destination_repo: '${{ github.actor }}/tekton-updates'
|
||||
destination_folder: 'latest'
|
||||
user_email: 'action@github.com'
|
||||
user_name: 'PatchBot'
|
||||
commit_message: '[AUTO] Pushed version checksum'
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Automated Testing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- 'feature/**'
|
||||
- 'patch-release'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@v1
|
||||
with:
|
||||
version: '4.3.0'
|
||||
use-dotnet: false
|
||||
|
||||
- name: Verify GUT Installation
|
||||
run: |
|
||||
if [ ! -d "addons/gut" ]; then
|
||||
echo "ERROR: GUT addon not found at addons/gut"
|
||||
exit 1
|
||||
fi
|
||||
echo "GUT addon found"
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: |
|
||||
godot --headless --path . -s res://addons/gut/gut_cmdln.gd \
|
||||
-gdir=res://tests/ \
|
||||
-gexit \
|
||||
-glog=2
|
||||
|
||||
- name: Check Test Results
|
||||
if: failure()
|
||||
run: |
|
||||
echo "Tests failed. Check logs above for details."
|
||||
exit 1
|
||||
|
||||
- name: Upload Test Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports
|
||||
path: test_reports/
|
||||
retention-days: 30
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"godotTools.editorPath.godot4": "c:\\Users\\beng\\Godot\\Editors\\4.6.2-stable\\Godot_v4.6.2-stable_win64.exe",
|
||||
"godotTools.editorPath.godot4": "/home/beng/Godot/Editors/4.6.3-stable/Godot_v4.6.3-stable_linux.x86_64",
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": false,
|
||||
"files.eol": "\n",
|
||||
|
||||
+4
-1
@@ -1,8 +1,11 @@
|
||||
## [2.3.6] — 2026-05-22
|
||||
## [2.3.7] — 2026-05-25
|
||||
- Added Candy Cannon timer, firing mechanics, and 5-shot volleys to Gauntlet.
|
||||
- Added new game mode: **Candy Cannon Survival** — dodge candy volleys from a giant cannon in the center of the arena!
|
||||
- New 20×20 arena with a central Candy Cannon obstacle and three escalating phases: Open Arena, Route Pressure, and Survival.
|
||||
- Candy Cannon Survival is now selectable from the lobby game mode list with its own dedicated arena.
|
||||
- The arena now spawns collectible tiles (Hearts, Diamonds, Stars, Coins) with pattern-matching missions — complete goals while dodging candy!
|
||||
- Integrated automated testing into CI pipeline — all commits now run unit and integration tests via GitHub Actions.
|
||||
- Set up complete GitHub Actions CI/CD pipeline with build, test, and deployment workflows for Windows, Android, and macOS platforms.
|
||||
|
||||
## [2.3.5] — 2026-05-22
|
||||
- Refactored `lobby.gd` into modular helper classes (`LobbyChat`, `LobbyMainMenu`, `LobbyRoomList`, `LobbyRoom`) to reduce file size and improve maintainability.
|
||||
|
||||
+30
-16
@@ -1,4 +1,4 @@
|
||||
# SKILLS.md — AI Agent Workflow Guide for Tekton Dash
|
||||
# AI Agent Workflow Guide for Tekton Dash
|
||||
|
||||
This document tells AI agents how to work on Tekton Dash tasks end-to-end.
|
||||
|
||||
@@ -8,21 +8,29 @@ This document tells AI agents how to work on Tekton Dash tasks end-to-end.
|
||||
|
||||
All tasks live on the **"TektonDash - Armageddon PR Tasks"** Notion board.
|
||||
|
||||
https://www.notion.so/danchiego-game/36433be43b29800c8422ed5bdd65671b?v=36633be43b29803891cd000c6f6e5c5f
|
||||
https://www.notion.so/danchiego-game/36433be43b29800c8422ed5bdd65671b?v=36433be43b2980de8635000c0a910a0d
|
||||
|
||||
### Finding Tasks
|
||||
|
||||
Should always start with this to find tasks, find the highest priority task that is not done, second is In Progress, then To Do.
|
||||
**CRITICAL:** Always start by finding tasks from Notion. Priority order: P0 > P1 > P2 > P3. Status order: In Progress > To Do.
|
||||
|
||||
example, query for "Gauntlet"
|
||||
Example search for "Gauntlet" tasks:
|
||||
```
|
||||
Use: mcp_notion-mcp-server_API-post-search
|
||||
Use: mcp_notion_API_post_search
|
||||
query: "[Gauntlet]" or task name
|
||||
filter: {"property": "object", "value": "page"}
|
||||
```
|
||||
|
||||
### Reading a Task
|
||||
|
||||
**CRITICAL:** The **Description** field contains the actual implementation requirements. TASKS.md defines workflow procedure only — each task's unique problem and solution are in Notion's Description field.
|
||||
|
||||
Always read full task details:
|
||||
```
|
||||
Use: mcp_notion_API_retrieve_a_page
|
||||
page_id: "<task_page_id_from_search>"
|
||||
```
|
||||
|
||||
Each task page has these properties:
|
||||
|
||||
| Property | Type | Purpose |
|
||||
@@ -44,15 +52,18 @@ Each task page has these properties:
|
||||
To Do → In Progress → Done
|
||||
```
|
||||
|
||||
1. **Pick up task**: Set `Status` → `In Progress`
|
||||
2. **Do the work**: Read `Description`, implement the changes
|
||||
3. **Write unit tests**: Follow pattern in `tests/` directory
|
||||
4. **Mark complete**: Set `Status` → `Done`, check `Acceptance` ✅
|
||||
5. **Update changelog**: Add entry to `CHANGELOG_DRAFT.md` (consumer language)
|
||||
6. **Bump version**: Update `project.godot` + `export_presets.cfg`
|
||||
**CRITICAL WORKFLOW:**
|
||||
|
||||
1. **Read task from Notion**: Use `mcp_notion_API_retrieve_a_page(page_id="...")` to get full task details
|
||||
2. **Read Description field carefully**: This contains the actual implementation requirements — file names, function signatures, integration points, RPC patterns, etc.
|
||||
3. **Implement exactly what Description specifies**: Don't invent your own approach — follow the Description's technical requirements
|
||||
4. **Write unit tests**: Follow pattern in `tests/` directory (see GUT_SETUP_SKILLS.md)
|
||||
5. **Update changelog**: Add entry to `CHANGELOG_DRAFT.md` (consumer-facing language, not technical jargon)
|
||||
6. **Version management**: Check git diff for existing version changes (see Version Bumping section below)
|
||||
7. **Mark complete in Notion**: Set `Status` → `Done`, check `Acceptance` ✅
|
||||
|
||||
```
|
||||
Use: mcp_notion-mcp-server_API-patch-page
|
||||
Use: mcp_notion_API_patch_page
|
||||
page_id: "<task_page_id>"
|
||||
properties: {"Status": {"select": {"name": "Done"}}, "Acceptance": {"checkbox": true}}
|
||||
```
|
||||
@@ -154,7 +165,10 @@ Entries are **consumer-facing** (readable by players). No internal jargon.
|
||||
## 5. Key Conventions
|
||||
|
||||
- **Caveman Mode**: Be terse. No filler. Execute first, talk second.
|
||||
- **Read before edit**: Always check whole files before modifying `.gd`, `.tscn`, `.tres`, `.res` files.
|
||||
- **Notion status flow**: `To Do` → `In Progress` → `Done` (never skip steps).
|
||||
- **Test everything**: Every completed task gets a `test_<feature>.gd` in `tests/`.
|
||||
- **GUT framework**: Tests use the Godot Unit Test (GUT) addon at `addons/gut/`.
|
||||
- **Read task from Notion FIRST**: Always use `mcp_notion_API_retrieve_a_page` to get the Description field before implementing
|
||||
- **Description field is the spec**: TASKS.md is workflow procedure only. Each task's unique requirements (file names, function signatures, RPC patterns, integration points) are in Notion's Description field
|
||||
- **Read before edit**: Always check whole files before modifying `.gd`, `.tscn`, `.tres`, `.res` files
|
||||
- **Notion status flow**: `To Do` → `In Progress` → `Done` (never skip steps)
|
||||
- **Test everything**: Every completed task gets a `test_<feature>.gd` in `tests/`
|
||||
- **GUT framework**: Tests use the Godot Unit Test (GUT) addon at `addons/gut/`
|
||||
- **Version discipline**: Check `git diff -- project.godot CHANGELOG_DRAFT.md` before bumping version (see Version Bumping section)
|
||||
@@ -16,6 +16,7 @@
|
||||
[ext_resource type="BoxMesh" uid="uid://b5cc3prem52r6" path="res://addons/enhanced_gridmap/meshlibrary/tile_freeze.tres" id="11_pgnbl"]
|
||||
[ext_resource type="BoxMesh" uid="uid://dcjdwbffgtutt" path="res://addons/enhanced_gridmap/meshlibrary/tile_non_walkable.tres" id="11_uwjsj"]
|
||||
[ext_resource type="Texture2D" uid="uid://cdnxwlysxnujd" path="res://assets/textures/tile_heart.png" id="12_heart_tex"]
|
||||
[ext_resource type="BoxMesh" uid="uid://tile_sticky_uid" path="res://addons/enhanced_gridmap/meshlibrary/tile_sticky.tres" id="13_sticky"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uxput"]
|
||||
resource_name = "boost"
|
||||
@@ -198,3 +199,10 @@ item/16/mesh_cast_shadow = 1
|
||||
item/16/shapes = []
|
||||
item/16/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
item/16/navigation_layers = 1
|
||||
item/17/name = "tile_sticky"
|
||||
item/17/mesh = ExtResource("13_sticky")
|
||||
item/17/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
item/17/mesh_cast_shadow = 1
|
||||
item/17/shapes = []
|
||||
item/17/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
item/17/navigation_layers = 1
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[gd_resource type="BoxMesh" format=3 uid="uid://tile_sticky_uid"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_sticky"]
|
||||
transparency = 1
|
||||
albedo_color = Color(1, 0.4, 0.7, 0.65)
|
||||
|
||||
[resource]
|
||||
material = SubResource("StandardMaterial3D_sticky")
|
||||
size = Vector3(1, 0.1, 1)
|
||||
@@ -0,0 +1,157 @@
|
||||
# Versioning & Artifact Storage Strategy
|
||||
|
||||
## Version Format
|
||||
|
||||
Tekton Armageddon uses semantic versioning: `MAJOR.MINOR.PATCH`
|
||||
|
||||
- **MAJOR**: Breaking changes, incompatible API changes
|
||||
- **MINOR**: New features, backward-compatible
|
||||
- **PATCH**: Bug fixes, content updates, backward-compatible
|
||||
|
||||
Current version source: `project.godot` → `config/version`
|
||||
|
||||
## Artifact Types
|
||||
|
||||
### 1. Patch PCK Files
|
||||
- **Purpose**: Hot-patch content updates without full client reinstall
|
||||
- **Storage**: GitHub Actions artifacts (90 days) + tekton-updates repo
|
||||
- **Naming**: `patch.pck` (latest), versioned in tekton-updates repo
|
||||
- **Integrity**: SHA256 checksums generated and stored alongside
|
||||
|
||||
### 2. Platform Builds
|
||||
- **Platforms**: Windows, Linux, Android
|
||||
- **Storage**: GitHub Actions artifacts (90 days) + GitHub Releases (permanent)
|
||||
- **Naming**: `tekton_armageddon_{Platform}_v{VERSION}.{ext}`
|
||||
- **Trigger**: Git tags (`v*.*.*`) or manual workflow dispatch
|
||||
- **Integrity**: SHA256 checksums for each platform build
|
||||
|
||||
### 3. Version Manifest
|
||||
- **File**: `assets/data/version.json`
|
||||
- **Purpose**: Client version checking, changelog delivery, patch URLs
|
||||
- **Storage**: Embedded in builds + tekton-updates repo
|
||||
- **Integrity**: SHA256 checksum
|
||||
|
||||
## Workflows
|
||||
|
||||
### Patch Deployment (`deploy_patch.yml`)
|
||||
**Trigger**: Push to `patch-release` branch or manual dispatch
|
||||
|
||||
**Process**:
|
||||
1. Auto-bump patch version from `project.godot`
|
||||
2. Extract changelog from `CHANGELOG_DRAFT.md` [NEXT] section
|
||||
3. Generate `version.json` with new release entry
|
||||
4. Commit version bump back to repo
|
||||
5. Build `patch.pck` from changed files
|
||||
6. Generate SHA256 checksums
|
||||
7. Upload artifacts to GitHub Actions (90-day retention)
|
||||
8. Push to `tekton-updates` public repo (`latest/` folder)
|
||||
|
||||
**Artifacts**:
|
||||
- `patch-pck-{SHA}`: patch.pck + checksum
|
||||
- `version-manifest-{SHA}`: version.json + checksum
|
||||
|
||||
### Platform Builds (`build_artifacts.yml`)
|
||||
**Trigger**: Git tag push (`v*.*.*`) or manual dispatch with version input
|
||||
|
||||
**Process**:
|
||||
1. Matrix build for Windows, Linux, Android
|
||||
2. Export using Godot export presets
|
||||
3. Generate SHA256 checksums per platform
|
||||
4. Upload artifacts to GitHub Actions (90-day retention)
|
||||
5. Create GitHub Release with all platform builds + checksums
|
||||
|
||||
**Artifacts**:
|
||||
- `tekton-{Platform}-v{VERSION}`: platform binary + checksum
|
||||
|
||||
## Artifact Retention
|
||||
|
||||
| Artifact Type | Storage Location | Retention | Purpose |
|
||||
|--------------|------------------|-----------|---------|
|
||||
| Patch PCK | GitHub Actions | 90 days | CI/CD history, rollback |
|
||||
| Patch PCK | tekton-updates repo | Permanent | Client downloads |
|
||||
| Platform Builds | GitHub Actions | 90 days | CI/CD history |
|
||||
| Platform Builds | GitHub Releases | Permanent | Distribution |
|
||||
| Version Manifest | tekton-updates repo | Permanent | Client version checks |
|
||||
|
||||
## Checksum Verification
|
||||
|
||||
All artifacts include SHA256 checksums for integrity verification:
|
||||
|
||||
```bash
|
||||
# Verify patch.pck
|
||||
sha256sum -c patch.pck.sha256
|
||||
|
||||
# Verify platform build
|
||||
sha256sum -c tekton_armageddon_Windows_v2.3.8.sha256
|
||||
```
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
`version.json` includes `minimum_app_version` field:
|
||||
- Clients below this version must reinstall full build
|
||||
- Clients at or above can use patch system
|
||||
|
||||
## Changelog Management
|
||||
|
||||
**Source**: `CHANGELOG_DRAFT.md`
|
||||
|
||||
**Format**:
|
||||
```markdown
|
||||
## [NEXT]
|
||||
- Feature or fix description
|
||||
- Another change
|
||||
|
||||
## [2.3.7] — 2026-05-15
|
||||
- Archived release notes
|
||||
```
|
||||
|
||||
**Process**:
|
||||
1. Developers add entries under `[NEXT]`
|
||||
2. CI extracts `[NEXT]` entries on patch deployment
|
||||
3. CI archives to versioned section
|
||||
4. CI clears `[NEXT]` for next cycle
|
||||
|
||||
## Manual Version Bump
|
||||
|
||||
For local testing without CI:
|
||||
|
||||
```bash
|
||||
# Update versions but keep changelog
|
||||
python3 tools/generate_version_json.py --local
|
||||
|
||||
# Skip changelog update entirely
|
||||
python3 tools/generate_version_json.py --skip-changelog
|
||||
```
|
||||
|
||||
## Release Process
|
||||
|
||||
### Patch Release
|
||||
1. Merge changes to `patch-release` branch
|
||||
2. CI auto-bumps version, builds, deploys
|
||||
3. Clients auto-download on next launch
|
||||
|
||||
### Full Release
|
||||
1. Tag commit: `git tag v2.4.0`
|
||||
2. Push tag: `git push origin v2.4.0`
|
||||
3. CI builds all platforms, creates GitHub Release
|
||||
4. Distribute via Steam, Google Play, etc.
|
||||
|
||||
## Rollback Strategy
|
||||
|
||||
**Patch Rollback**:
|
||||
1. Locate previous patch in GitHub Actions artifacts or tekton-updates repo
|
||||
2. Manually push to `tekton-updates/latest/`
|
||||
3. Update `version.json` to point to previous version
|
||||
|
||||
**Full Build Rollback**:
|
||||
1. Download previous release from GitHub Releases
|
||||
2. Re-tag or create hotfix branch
|
||||
3. Redeploy via standard release process
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Automated rollback on failed health checks
|
||||
- [ ] Delta patching for bandwidth optimization
|
||||
- [ ] Multi-region CDN distribution
|
||||
- [ ] Staged rollout (canary deployments)
|
||||
- [ ] Automated compatibility testing matrix
|
||||
+9
-9
@@ -8,7 +8,7 @@ custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="build/tekton_armageddon_v2.3.6.exe"
|
||||
export_path="build/tekton_armageddon_v2.3.7.exe"
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
@@ -42,8 +42,8 @@ application/modify_resources=false
|
||||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version="2.3.6"
|
||||
application/product_version="2.3.6"
|
||||
application/file_version="2.3.7"
|
||||
application/product_version="2.3.7"
|
||||
application/company_name="DanchieGo"
|
||||
application/product_name="Tekton Armageddon"
|
||||
application/file_description=""
|
||||
@@ -80,7 +80,7 @@ custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="build/tekton-dash-armageddon-v.2.3.6.apk"
|
||||
export_path="build/tekton-dash-armageddon-v.2.3.7.apk"
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
@@ -111,7 +111,7 @@ architectures/arm64-v8a=true
|
||||
architectures/x86=false
|
||||
architectures/x86_64=false
|
||||
version/code=3
|
||||
version/name="2.3.6"
|
||||
version/name="2.3.7"
|
||||
package/unique_name="com.danchiego.$genname"
|
||||
package/name="Tekton Dash Armageddon"
|
||||
package/signed=true
|
||||
@@ -306,7 +306,7 @@ custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="build/tekton_armageddon_v2.3.6.zip"
|
||||
export_path="build/tekton_armageddon_v2.3.7.zip"
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
@@ -565,8 +565,8 @@ codesign/digest_algorithm=1
|
||||
codesign/identity_type=0
|
||||
application/modify_resources=false
|
||||
application/console_wrapper_icon=""
|
||||
application/file_version="2.3.6"
|
||||
application/product_version="2.3.6"
|
||||
application/file_version="2.3.7"
|
||||
application/product_version="2.3.7"
|
||||
application/company_name="DanchieGo"
|
||||
application/product_name="Tekton Armageddon"
|
||||
application/file_description=""
|
||||
@@ -582,7 +582,7 @@ custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="build/tekton_armageddon_v2.3.6.x86_64"
|
||||
export_path="build/tekton_armageddon_v2.3.7.x86_64"
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
||||
[application]
|
||||
|
||||
config/name="Tekton Dash Armageddon"
|
||||
config/version="2.3.6"
|
||||
config/version="2.3.7"
|
||||
run/main_scene="res://scenes/ui/boot_screen.tscn"
|
||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||
boot_splash/bg_color=Color(0.16470589, 0.6745098, 0.9372549, 1)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://ddy2r7xto80gq"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/controllers/candy_cannon_controller.gd" id="1_canon"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_canon"]
|
||||
size = Vector3(1.5, 3, 1.5)
|
||||
|
||||
[node name="CandyCannon" type="Node3D"]
|
||||
script = ExtResource("1_canon")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
mesh = SubResource("BoxMesh_canon")
|
||||
@@ -0,0 +1,47 @@
|
||||
extends Node3D
|
||||
class_name CandyCannonController
|
||||
|
||||
@export var is_static_turret: bool = true
|
||||
|
||||
func _ready() -> void:
|
||||
pass
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func play_animation_rpc(anim_name: String) -> void:
|
||||
# Stub for future model animations
|
||||
pass
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func spawn_projectile_rpc(target_world_pos: Vector3, duration: float) -> void:
|
||||
var projectile = MeshInstance3D.new()
|
||||
var sphere = BoxMesh.new()
|
||||
sphere.size = Vector3(0.4, 0.4, 0.4)
|
||||
projectile.mesh = sphere
|
||||
var mat = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(1.0, 0.4, 0.8) # Candy pink for Gauntlet
|
||||
projectile.material_override = mat
|
||||
|
||||
get_tree().get_root().add_child(projectile)
|
||||
|
||||
# Start projectile slightly above the cannon center
|
||||
projectile.global_position = global_position + Vector3(0, 2.0, 0)
|
||||
|
||||
var tween = create_tween()
|
||||
if not tween:
|
||||
projectile.queue_free()
|
||||
return
|
||||
|
||||
tween.set_parallel(true)
|
||||
tween.tween_property(projectile, "global_position:x", target_world_pos.x, duration).set_trans(Tween.TRANS_LINEAR)
|
||||
tween.tween_property(projectile, "global_position:z", target_world_pos.z, duration).set_trans(Tween.TRANS_LINEAR)
|
||||
|
||||
var mid_y = max(global_position.y, target_world_pos.y) + 4.0
|
||||
var tween_y = create_tween()
|
||||
tween_y.tween_property(projectile, "global_position:y", mid_y, duration / 2.0).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
tween_y.tween_property(projectile, "global_position:y", target_world_pos.y, duration / 2.0).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN).set_delay(duration / 2.0)
|
||||
|
||||
tween.chain().tween_callback(projectile.queue_free)
|
||||
|
||||
func can_rpc() -> bool:
|
||||
if not multiplayer.has_multiplayer_peer(): return false
|
||||
return multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED
|
||||
@@ -86,6 +86,8 @@ var trapped_players: Dictionary = {} # player_id → true
|
||||
|
||||
var main_scene: Node = null
|
||||
var gridmap: Node = null
|
||||
var candy_cannon_scene: PackedScene = preload("res://scenes/candy_cannon.tscn")
|
||||
var cannon_instance: Node3D = null
|
||||
|
||||
# HUD
|
||||
var hud_layer: CanvasLayer
|
||||
@@ -250,6 +252,14 @@ func _apply_arena_setup() -> void:
|
||||
gridmap.update_grid_data()
|
||||
gridmap.initialize_astar()
|
||||
|
||||
if not cannon_instance and main_scene:
|
||||
cannon_instance = candy_cannon_scene.instantiate()
|
||||
cannon_instance.name = "CandyCannon"
|
||||
var cx = NPC_CENTER.x * gridmap.cell_size.x + gridmap.cell_size.x / 2.0
|
||||
var cz = NPC_CENTER.y * gridmap.cell_size.z + gridmap.cell_size.z / 2.0
|
||||
cannon_instance.position = Vector3(cx, 0, cz)
|
||||
main_scene.add_child(cannon_instance)
|
||||
|
||||
print("[Gauntlet] Arena setup complete. Center NPC at (%d,%d), size %dx%d" % [
|
||||
NPC_CENTER.x, NPC_CENTER.y, NPC_SIZE, NPC_SIZE
|
||||
])
|
||||
@@ -336,6 +346,13 @@ func _fire_volley() -> void:
|
||||
# Telegraph phase — show warning
|
||||
if _can_rpc():
|
||||
rpc("sync_telegraph", targets)
|
||||
|
||||
# Shoot projectiles visually
|
||||
if cannon_instance and cannon_instance.has_method("spawn_projectile_rpc") and cannon_instance.can_rpc():
|
||||
var cs = gridmap.cell_size
|
||||
for target in targets:
|
||||
var target_pos = Vector3(target.x * cs.x + cs.x / 2.0, 0, target.y * cs.z + cs.z / 2.0)
|
||||
cannon_instance.rpc("spawn_projectile_rpc", target_pos, telegraph_time)
|
||||
|
||||
# Wait telegraph duration, then apply impact
|
||||
await get_tree().create_timer(telegraph_time).timeout
|
||||
@@ -443,24 +460,34 @@ func _check_all_players_trapped() -> void:
|
||||
var all_players = get_tree().get_nodes_in_group("Players")
|
||||
for player in all_players:
|
||||
var pos = player.current_position if player.get("current_position") else Vector2i(-1, -1)
|
||||
if is_sticky_cell(pos) and not trapped_players.has(player.get("peer_id", -1)):
|
||||
if is_sticky_cell(pos) and not trapped_players.has(player.get("peer_id") if "peer_id" in player else -1):
|
||||
_trap_player(player)
|
||||
|
||||
func _trap_player(player: Node) -> void:
|
||||
var pid = player.get("peer_id", -1)
|
||||
var pid = player.get("peer_id") if "peer_id" in player else -1
|
||||
if pid == -1: return
|
||||
trapped_players[pid] = true
|
||||
print("[Gauntlet] Player %d TRAPPED at %s" % [pid, str(player.current_position)])
|
||||
emit_signal("player_trapped", pid)
|
||||
|
||||
# TODO: Apply movement lockout, score penalty, visual feedback
|
||||
# For now, just mark as trapped — will be expanded in Task #4
|
||||
# Apply visual feedback and notify
|
||||
if player.has_method("apply_stagger"):
|
||||
if _can_rpc():
|
||||
player.rpc("apply_stagger", 999.0) # Basically infinite until cleansed
|
||||
else:
|
||||
player.apply_stagger(999.0)
|
||||
|
||||
NotificationManager.send_message(player, "Stuck in Candy!", NotificationManager.MessageType.WARNING)
|
||||
|
||||
func clear_sticky_cell(pos: Vector2i) -> void:
|
||||
"""Used by Cleanser power-up to remove a sticky cell."""
|
||||
sticky_cells.erase(pos)
|
||||
if gridmap:
|
||||
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), -1)
|
||||
|
||||
# Sync removal to clients
|
||||
if main_scene and _can_rpc():
|
||||
main_scene.rpc("sync_grid_item", pos.x, 2, pos.y, -1)
|
||||
|
||||
# =============================================================================
|
||||
# HUD
|
||||
|
||||
@@ -131,6 +131,18 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
||||
if not try_push(grid_position, push_dir):
|
||||
return false
|
||||
|
||||
var gm = null
|
||||
var main = player.get_tree().root.get_node_or_null("Main")
|
||||
if main and main.get("gauntlet_manager"):
|
||||
gm = main.gauntlet_manager
|
||||
|
||||
# Check if currently trapped
|
||||
if gm and gm.is_active:
|
||||
var pid = player.get("peer_id") if "peer_id" in player else -1
|
||||
if pid != -1 and gm.trapped_players.has(pid):
|
||||
print("[Move] Failed: Player is trapped in a sticky cell")
|
||||
return false
|
||||
|
||||
# Check for Tekton interaction (Knock Mode)
|
||||
# If moving into a Tekton's space while in Knock Mode, trigger knock
|
||||
if player.get("is_knock_mode"):
|
||||
@@ -142,6 +154,12 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
||||
player.knock_tekton()
|
||||
return false # Don't move into the tile, just knock
|
||||
|
||||
# If moving into a sticky cell, trigger trap
|
||||
if gm and gm.is_active and gm.is_sticky_cell(grid_position):
|
||||
print("[Move] Player stepping into sticky cell at %s" % grid_position)
|
||||
movement_queue.clear()
|
||||
if player.is_multiplayer_authority() or multiplayer.is_server():
|
||||
gm._trap_player(player)
|
||||
|
||||
rotate_towards_target(grid_position)
|
||||
|
||||
@@ -249,6 +267,15 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
|
||||
|
||||
other_player.target_position = pushed_to_pos # Logical update
|
||||
|
||||
# Check if landing spot is sticky
|
||||
var main = player.get_tree().root.get_node_or_null("Main")
|
||||
if main and main.get("gauntlet_manager"):
|
||||
var gm = main.gauntlet_manager
|
||||
if gm.is_active and gm.is_sticky_cell(pushed_to_pos):
|
||||
print("[Move] Player pushed into sticky cell at %s" % pushed_to_pos)
|
||||
if multiplayer.is_server() or other_player.is_multiplayer_authority():
|
||||
gm._trap_player(other_player)
|
||||
|
||||
# 2. Apply freeze/stun effect (blue tint)
|
||||
if _can_rpc():
|
||||
other_player.rpc("apply_stagger", 1.5)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
extends RefCounted
|
||||
class_name ModeConfig
|
||||
|
||||
# ModeConfig - Schema-driven validation for game mode settings
|
||||
# Task [036]: Consolidates duplicated/inconsistent option toggles
|
||||
|
||||
# Schema definition for all mode-specific settings
|
||||
const SCHEMA = {
|
||||
"Freemode": {
|
||||
"match_duration": {"type": TYPE_INT, "default": 180, "min": 60, "max": 600},
|
||||
"randomize_spawn": {"type": TYPE_BOOL, "default": false},
|
||||
"enable_cycle_timer": {"type": TYPE_BOOL, "default": false},
|
||||
"scarcity_mode": {"type": TYPE_STRING, "default": "Normal", "allowed": ["Normal", "Aggressive", "Chaos"]}
|
||||
},
|
||||
"Stop n Go": {
|
||||
"match_duration": {"type": TYPE_INT, "default": 180, "min": 60, "max": 600},
|
||||
"sng_go_duration": {"type": TYPE_INT, "default": 20, "min": 10, "max": 60},
|
||||
"sng_stop_duration": {"type": TYPE_INT, "default": 4, "min": 2, "max": 10},
|
||||
"sng_required_goals": {"type": TYPE_INT, "default": 8, "min": 3, "max": 20}
|
||||
},
|
||||
"Tekton Doors": {
|
||||
"match_duration": {"type": TYPE_INT, "default": 180, "min": 60, "max": 600},
|
||||
"doors_swap_time": {"type": TYPE_INT, "default": 15, "min": 10, "max": 30},
|
||||
"doors_refresh_time": {"type": TYPE_INT, "default": 25, "min": 15, "max": 40},
|
||||
"doors_required_goals": {"type": TYPE_INT, "default": 8, "min": 5, "max": 12}
|
||||
},
|
||||
"Candy Cannon Survival": {
|
||||
"match_duration": {"type": TYPE_INT, "default": 180, "min": 60, "max": 600},
|
||||
"gauntlet_cannon_interval": {"type": TYPE_FLOAT, "default": 5.0, "min": 2.0, "max": 10.0},
|
||||
"gauntlet_volley_size": {"type": TYPE_INT, "default": 5, "min": 3, "max": 15}
|
||||
}
|
||||
}
|
||||
|
||||
# Get default config for a mode
|
||||
static func get_defaults(mode: String) -> Dictionary:
|
||||
if not SCHEMA.has(mode):
|
||||
push_error("ModeConfig: Unknown mode '%s'" % mode)
|
||||
return {}
|
||||
|
||||
var defaults = {}
|
||||
for key in SCHEMA[mode]:
|
||||
defaults[key] = SCHEMA[mode][key]["default"]
|
||||
return defaults
|
||||
|
||||
# Validate a single setting
|
||||
static func validate_setting(mode: String, key: String, value: Variant) -> Dictionary:
|
||||
if not SCHEMA.has(mode):
|
||||
return {"valid": false, "error": "Unknown mode: %s" % mode}
|
||||
|
||||
if not SCHEMA[mode].has(key):
|
||||
return {"valid": false, "error": "Unknown setting '%s' for mode '%s'" % [key, mode]}
|
||||
|
||||
var schema = SCHEMA[mode][key]
|
||||
|
||||
# Type check
|
||||
if typeof(value) != schema["type"]:
|
||||
return {"valid": false, "error": "Setting '%s' expects type %s, got %s" % [key, schema["type"], typeof(value)]}
|
||||
|
||||
# Range check for numbers
|
||||
if schema["type"] == TYPE_INT or schema["type"] == TYPE_FLOAT:
|
||||
if schema.has("min") and value < schema["min"]:
|
||||
return {"valid": false, "error": "Setting '%s' must be >= %s" % [key, schema["min"]]}
|
||||
if schema.has("max") and value > schema["max"]:
|
||||
return {"valid": false, "error": "Setting '%s' must be <= %s" % [key, schema["max"]]}
|
||||
|
||||
# Allowed values check for strings
|
||||
if schema["type"] == TYPE_STRING and schema.has("allowed"):
|
||||
if value not in schema["allowed"]:
|
||||
return {"valid": false, "error": "Setting '%s' must be one of %s" % [key, schema["allowed"]]}
|
||||
|
||||
return {"valid": true}
|
||||
|
||||
# Validate entire config for a mode
|
||||
static func validate_config(mode: String, config: Dictionary) -> Dictionary:
|
||||
if not SCHEMA.has(mode):
|
||||
return {"valid": false, "error": "Unknown mode: %s" % mode}
|
||||
|
||||
var errors = []
|
||||
|
||||
for key in config:
|
||||
var result = validate_setting(mode, key, config[key])
|
||||
if not result["valid"]:
|
||||
errors.append(result["error"])
|
||||
|
||||
if errors.is_empty():
|
||||
return {"valid": true}
|
||||
else:
|
||||
return {"valid": false, "errors": errors}
|
||||
|
||||
# Get all settings for a mode
|
||||
static func get_mode_settings(mode: String) -> Array:
|
||||
if not SCHEMA.has(mode):
|
||||
return []
|
||||
return SCHEMA[mode].keys()
|
||||
|
||||
# Get schema for a specific setting
|
||||
static func get_setting_schema(mode: String, key: String) -> Dictionary:
|
||||
if not SCHEMA.has(mode) or not SCHEMA[mode].has(key):
|
||||
return {}
|
||||
return SCHEMA[mode][key]
|
||||
|
||||
# Check if a mode has a specific setting
|
||||
static func has_setting(mode: String, key: String) -> bool:
|
||||
return SCHEMA.has(mode) and SCHEMA[mode].has(key)
|
||||
|
||||
# Get all supported modes
|
||||
static func get_supported_modes() -> Array:
|
||||
return SCHEMA.keys()
|
||||
@@ -0,0 +1,41 @@
|
||||
extends GutTest
|
||||
|
||||
const GauntletManager = preload("res://scripts/managers/gauntlet_manager.gd")
|
||||
var gauntlet_manager: Node
|
||||
var main_mock: Node
|
||||
var gridmap_mock: Node
|
||||
|
||||
func before_all():
|
||||
gut.p("=== Feature Tests [Gauntlet #4 Cannon Timer] ===")
|
||||
|
||||
func before_each():
|
||||
main_mock = Node.new()
|
||||
add_child(main_mock)
|
||||
gridmap_mock = Node.new()
|
||||
gridmap_mock.name = "EnhancedGridMap"
|
||||
main_mock.add_child(gridmap_mock)
|
||||
|
||||
gauntlet_manager = GauntletManager.new()
|
||||
main_mock.add_child(gauntlet_manager)
|
||||
gauntlet_manager.initialize(main_mock, gridmap_mock)
|
||||
|
||||
func test_cannon_timer_initialization():
|
||||
assert_eq(gauntlet_manager.cannon_timer, 0.0, "Timer should start at 0.0 before phase starts")
|
||||
|
||||
# Manually start phase to setup interval
|
||||
gauntlet_manager.current_phase = 0 # GauntletManager.Phase.OPEN_ARENA
|
||||
var config = gauntlet_manager.phase_configs[0]
|
||||
gauntlet_manager.cannon_interval = config["interval"]
|
||||
gauntlet_manager.cannon_timer = gauntlet_manager.cannon_interval
|
||||
|
||||
assert_eq(gauntlet_manager.cannon_timer, 5.0, "Timer should initialize to Phase 1 interval (5.0)")
|
||||
|
||||
func test_volley_size_configuration():
|
||||
assert_eq(gauntlet_manager.phase_configs[0]["volley"], 5, "Phase 1 volley size should be 5")
|
||||
|
||||
func after_each():
|
||||
if main_mock:
|
||||
main_mock.queue_free()
|
||||
|
||||
func after_all():
|
||||
gut.p("=== Feature Tests Complete ===")
|
||||
Reference in New Issue
Block a user