Compare commits
77 Commits
8881f442bb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ce5c2ef02 | |||
| db2cad10d8 | |||
| 35ee2f9a5e | |||
|
|
1f1c394587 | ||
| 3b8da89ad5 | |||
| 538459f323 | |||
| 550b2ac220 | |||
| f6475f83f6 | |||
| 35151cecf1 | |||
| dde7b98ed2 | |||
| d1761a2464 | |||
| ffd88c02e1 | |||
| bd9b7c009a | |||
| e61ab94935 | |||
| 9150622e74 | |||
| 501cad6175 | |||
| 5275c5ca94 | |||
| 61951a047b | |||
| 5f6a3ae175 | |||
| 40c06ae249 | |||
| 1189ce0931 | |||
| ff04b6ee22 | |||
| ff0a4fefe1 | |||
| 666823c641 | |||
| 02f2bb2703 | |||
| 38e85c2a24 | |||
| e31278e389 | |||
| 024343db19 | |||
| ad7a2575da | |||
| 26991ce61a | |||
| 8ded8c81ee | |||
| eb99c6a18e | |||
| c1f3f9f708 | |||
| e2e49f89ce | |||
| 7ec75a3b26 | |||
| 33a25dc532 | |||
| 60279542e1 | |||
| 35bdd44649 | |||
| 77971497a4 | |||
| 9b83bec37d | |||
| 0791b80f15 | |||
| ca6111cd28 | |||
| 0cf76d595f | |||
| 821d9aa42c | |||
| 06f0f87970 | |||
| 86439abea8 | |||
| dd0c1a123c | |||
| 3e960a955c | |||
| ca233f4171 | |||
| ea8c85d7ad | |||
| e76297b3f3 | |||
| 14d5e8fd8f | |||
| 7b0db3abff | |||
| 83cc433c2f | |||
| 7182c45351 | |||
| e11e864b26 | |||
| d8435ef46c | |||
| 9b3a20cdf1 | |||
| 4e0b5fe3d2 | |||
| 4596bc203a | |||
| 78a61fe33c | |||
| d9084606c8 | |||
| 7951a42c88 | |||
| 2589f9f73f | |||
| a1f8536855 | |||
| c57a08f5da | |||
| 542cf6a4f1 | |||
| 1b95573720 | |||
| dac3d42b6b | |||
| e732f9b6da | |||
| c52d861e63 | |||
| 54c23a83e5 | |||
| 7882e70e03 | |||
| 20440fe07f | |||
| f949502ef4 | |||
| d7a0494d7e | |||
| fe69347625 |
15
.claude/settings.local.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"WebSearch",
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(godot:*)",
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(git mv:*)",
|
||||||
|
"Bash(dir:*)",
|
||||||
|
"Bash(yamllint:*)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
19
.gdformatrc
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# GDFormat configuration file (YAML format)
|
||||||
|
# This file configures the gdformat tool for consistent GDScript formatting
|
||||||
|
|
||||||
|
# Maximum line length (default is 100)
|
||||||
|
# Godot's style guide recommends keeping lines under 100 characters
|
||||||
|
line_length: 80
|
||||||
|
|
||||||
|
# Use spaces instead of tabs (null = use tabs)
|
||||||
|
# Set to integer for space count, or null for tabs
|
||||||
|
# Godot uses tabs by default
|
||||||
|
use_spaces: null
|
||||||
|
|
||||||
|
# Safety checks (null = enabled by default)
|
||||||
|
safety_checks: null
|
||||||
|
|
||||||
|
# Directories to exclude from formatting
|
||||||
|
excluded_directories: !!set
|
||||||
|
.git: null
|
||||||
|
.godot: null
|
||||||
649
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,649 @@
|
|||||||
|
name: Build Game
|
||||||
|
|
||||||
|
# Build pipeline for creating game executables across multiple platforms
|
||||||
|
#
|
||||||
|
# Features:
|
||||||
|
# - Manual trigger with individual platform checkboxes
|
||||||
|
# - Configurable version override (defaults to auto-generated)
|
||||||
|
# - Configurable tool versions (Godot, Java, Android API, etc.)
|
||||||
|
# - Flexible runner OS selection
|
||||||
|
# - Tag-based automatic builds for releases
|
||||||
|
# - Multi-platform builds (Windows, Linux, macOS, Android)
|
||||||
|
# - Artifact storage for one week
|
||||||
|
# - Comprehensive build configuration options
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Manual trigger with platform selection
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
build_windows:
|
||||||
|
description: 'Build for Windows'
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
build_linux:
|
||||||
|
description: 'Build for Linux'
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
build_macos:
|
||||||
|
description: 'Build for macOS'
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
build_android:
|
||||||
|
description: 'Build for Android'
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
version:
|
||||||
|
description: 'Version (leave empty for auto-generated)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
build_type:
|
||||||
|
description: 'Build type'
|
||||||
|
required: true
|
||||||
|
default: 'release'
|
||||||
|
type: debug
|
||||||
|
options:
|
||||||
|
- release
|
||||||
|
- debug
|
||||||
|
godot_version:
|
||||||
|
description: 'Godot version (leave empty for default)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
runner_os:
|
||||||
|
description: 'Runner OS (leave empty for default ubuntu-latest)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
java_version:
|
||||||
|
description: 'Java version (leave empty for default)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
android_api_level:
|
||||||
|
description: 'Android API level (leave empty for default)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
|
||||||
|
# Automatic trigger on git tags (for releases)
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*' # Version tags (v1.0.0, v2.1.0, etc.)
|
||||||
|
- 'release-*' # Release tags
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Core Configuration
|
||||||
|
GODOT_VERSION: "4.4.1"
|
||||||
|
PROJECT_NAME: "Skelly"
|
||||||
|
BUILD_DIR: "builds"
|
||||||
|
DEFAULT_VERSION: "1.0.0-dev"
|
||||||
|
|
||||||
|
# GitHub Actions Versions
|
||||||
|
ACTIONS_CHECKOUT_VERSION: "v4"
|
||||||
|
ACTIONS_CACHE_VERSION: "v4"
|
||||||
|
ACTIONS_UPLOAD_ARTIFACT_VERSION: "v3"
|
||||||
|
ACTIONS_SETUP_JAVA_VERSION: "v4"
|
||||||
|
|
||||||
|
# Third-party Actions Versions
|
||||||
|
CHICKENSOFT_SETUP_GODOT_VERSION: "v1"
|
||||||
|
ANDROID_ACTIONS_SETUP_ANDROID_VERSION: "v3"
|
||||||
|
|
||||||
|
# Runner Configuration
|
||||||
|
RUNNER_OS: "ubuntu-latest"
|
||||||
|
|
||||||
|
# Java Configuration
|
||||||
|
JAVA_DISTRIBUTION: "temurin"
|
||||||
|
JAVA_VERSION: "17"
|
||||||
|
|
||||||
|
# Android Configuration
|
||||||
|
ANDROID_API_LEVEL: "33"
|
||||||
|
ANDROID_BUILD_TOOLS_VERSION: "33.0.0"
|
||||||
|
ANDROID_CMDLINE_TOOLS_VERSION: "latest"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Preparation job - determines build configuration
|
||||||
|
prepare:
|
||||||
|
name: Prepare Build
|
||||||
|
runs-on: ${{ env.RUNNER_OS }}
|
||||||
|
outputs:
|
||||||
|
platforms: ${{ steps.config.outputs.platforms }}
|
||||||
|
build_type: ${{ steps.config.outputs.build_type }}
|
||||||
|
version: ${{ steps.config.outputs.version }}
|
||||||
|
artifact_name: ${{ steps.config.outputs.artifact_name }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure build parameters
|
||||||
|
id: config
|
||||||
|
run: |
|
||||||
|
# Override environment variables with user inputs if provided
|
||||||
|
if [[ -n "${{ github.event.inputs.godot_version }}" ]]; then
|
||||||
|
echo "GODOT_VERSION=${{ github.event.inputs.godot_version }}" >> $GITHUB_ENV
|
||||||
|
echo "🔧 Using custom Godot version: ${{ github.event.inputs.godot_version }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${{ github.event.inputs.runner_os }}" ]]; then
|
||||||
|
echo "RUNNER_OS=${{ github.event.inputs.runner_os }}" >> $GITHUB_ENV
|
||||||
|
echo "🔧 Using custom runner OS: ${{ github.event.inputs.runner_os }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${{ github.event.inputs.java_version }}" ]]; then
|
||||||
|
echo "JAVA_VERSION=${{ github.event.inputs.java_version }}" >> $GITHUB_ENV
|
||||||
|
echo "🔧 Using custom Java version: ${{ github.event.inputs.java_version }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${{ github.event.inputs.android_api_level }}" ]]; then
|
||||||
|
echo "ANDROID_API_LEVEL=${{ github.event.inputs.android_api_level }}" >> $GITHUB_ENV
|
||||||
|
echo "ANDROID_BUILD_TOOLS_VERSION=${{ github.event.inputs.android_api_level }}.0.0" >> $GITHUB_ENV
|
||||||
|
echo "🔧 Using custom Android API level: ${{ github.event.inputs.android_api_level }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine platforms to build
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
# Build platforms array from individual checkboxes
|
||||||
|
platforms=""
|
||||||
|
if [[ "${{ github.event.inputs.build_windows }}" == "true" ]]; then
|
||||||
|
platforms="${platforms}windows,"
|
||||||
|
fi
|
||||||
|
if [[ "${{ github.event.inputs.build_linux }}" == "true" ]]; then
|
||||||
|
platforms="${platforms}linux,"
|
||||||
|
fi
|
||||||
|
if [[ "${{ github.event.inputs.build_macos }}" == "true" ]]; then
|
||||||
|
platforms="${platforms}macos,"
|
||||||
|
fi
|
||||||
|
if [[ "${{ github.event.inputs.build_android }}" == "true" ]]; then
|
||||||
|
platforms="${platforms}android,"
|
||||||
|
fi
|
||||||
|
# Remove trailing comma
|
||||||
|
platforms="${platforms%,}"
|
||||||
|
|
||||||
|
build_type="${{ github.event.inputs.build_type }}"
|
||||||
|
user_version="${{ github.event.inputs.version }}"
|
||||||
|
else
|
||||||
|
# Tag-triggered build - build all platforms
|
||||||
|
platforms="windows,linux,macos,android"
|
||||||
|
build_type="release"
|
||||||
|
user_version=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine version with improved logic
|
||||||
|
if [[ -n "$user_version" ]]; then
|
||||||
|
# User provided explicit version
|
||||||
|
version="$user_version"
|
||||||
|
echo "🏷️ Using user-specified version: $version"
|
||||||
|
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||||
|
# Tag-triggered build - use tag name
|
||||||
|
version="${{ github.ref_name }}"
|
||||||
|
echo "🏷️ Using git tag version: $version"
|
||||||
|
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
# Manual dispatch without version - use default + git info
|
||||||
|
commit_short=$(git rev-parse --short HEAD)
|
||||||
|
branch_name="${{ github.ref_name }}"
|
||||||
|
timestamp=$(date +%Y%m%d-%H%M)
|
||||||
|
version="${{ env.DEFAULT_VERSION }}-${branch_name}-${commit_short}-${timestamp}"
|
||||||
|
echo "🏷️ Using auto-generated version: $version"
|
||||||
|
else
|
||||||
|
# Fallback for other triggers
|
||||||
|
commit_short=$(git rev-parse --short HEAD)
|
||||||
|
branch_name="${{ github.ref_name }}"
|
||||||
|
timestamp=$(date +%Y%m%d-%H%M)
|
||||||
|
version="${{ env.DEFAULT_VERSION }}-${branch_name}-${commit_short}-${timestamp}"
|
||||||
|
echo "🏷️ Using fallback version: $version"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create artifact name
|
||||||
|
artifact_name="${{ env.PROJECT_NAME }}-${version}-${build_type}"
|
||||||
|
|
||||||
|
echo "platforms=${platforms}" >> $GITHUB_OUTPUT
|
||||||
|
echo "build_type=${build_type}" >> $GITHUB_OUTPUT
|
||||||
|
echo "version=${version}" >> $GITHUB_OUTPUT
|
||||||
|
echo "artifact_name=${artifact_name}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
echo "🔧 Build Configuration:"
|
||||||
|
echo " Platforms: ${platforms}"
|
||||||
|
echo " Build Type: ${build_type}"
|
||||||
|
echo " Version: ${version}"
|
||||||
|
echo " Artifact: ${artifact_name}"
|
||||||
|
echo ""
|
||||||
|
echo "🔧 Tool Versions:"
|
||||||
|
echo " Godot: ${GODOT_VERSION}"
|
||||||
|
echo " Runner OS: ${RUNNER_OS}"
|
||||||
|
echo " Java: ${JAVA_VERSION}"
|
||||||
|
echo " Android API: ${ANDROID_API_LEVEL}"
|
||||||
|
echo " Android Build Tools: ${ANDROID_BUILD_TOOLS_VERSION}"
|
||||||
|
|
||||||
|
# Setup export templates (shared across all platform builds)
|
||||||
|
setup-templates:
|
||||||
|
name: Setup Export Templates
|
||||||
|
runs-on: ${{ env.RUNNER_OS }}
|
||||||
|
needs: prepare
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Cache export templates
|
||||||
|
id: cache-templates
|
||||||
|
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||||
|
with:
|
||||||
|
path: ~/.local/share/godot/export_templates
|
||||||
|
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||||
|
restore-keys: |
|
||||||
|
godot-templates-
|
||||||
|
|
||||||
|
- name: Setup Godot
|
||||||
|
if: steps.cache-templates.outputs.cache-hit != 'true'
|
||||||
|
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||||
|
with:
|
||||||
|
version: ${{ env.GODOT_VERSION }}
|
||||||
|
use-dotnet: false
|
||||||
|
|
||||||
|
- name: Install export templates
|
||||||
|
if: steps.cache-templates.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
echo "📦 Installing Godot export templates..."
|
||||||
|
mkdir -p ~/.local/share/godot/export_templates/${{ env.GODOT_VERSION }}.stable
|
||||||
|
wget -q https://github.com/godotengine/godot/releases/download/${{ env.GODOT_VERSION }}-stable/Godot_v${{ env.GODOT_VERSION }}-stable_export_templates.tpz
|
||||||
|
unzip -q Godot_v${{ env.GODOT_VERSION }}-stable_export_templates.tpz
|
||||||
|
mv templates/* ~/.local/share/godot/export_templates/${{ env.GODOT_VERSION }}.stable/
|
||||||
|
echo "✅ Export templates installed successfully"
|
||||||
|
ls -la ~/.local/share/godot/export_templates/${{ env.GODOT_VERSION }}.stable/
|
||||||
|
|
||||||
|
- name: Verify templates cache
|
||||||
|
run: |
|
||||||
|
echo "🔍 Verifying export templates are available:"
|
||||||
|
ls -la ~/.local/share/godot/export_templates/${{ env.GODOT_VERSION }}.stable/
|
||||||
|
|
||||||
|
# Windows build job
|
||||||
|
build-windows:
|
||||||
|
name: Build Windows
|
||||||
|
runs-on: ${{ env.RUNNER_OS }}
|
||||||
|
needs: [prepare, setup-templates]
|
||||||
|
if: contains(needs.prepare.outputs.platforms, 'windows')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Godot
|
||||||
|
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||||
|
with:
|
||||||
|
version: ${{ env.GODOT_VERSION }}
|
||||||
|
use-dotnet: false
|
||||||
|
|
||||||
|
- name: Restore export templates cache
|
||||||
|
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||||
|
with:
|
||||||
|
path: ~/.local/share/godot/export_templates
|
||||||
|
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||||
|
restore-keys: |
|
||||||
|
godot-templates-
|
||||||
|
|
||||||
|
- name: Create build directory
|
||||||
|
run: mkdir -p ${{ env.BUILD_DIR }}
|
||||||
|
|
||||||
|
- name: Import project assets
|
||||||
|
run: |
|
||||||
|
echo "📦 Importing project assets..."
|
||||||
|
godot --headless --verbose --editor --quit || true
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
- name: Build Windows executable
|
||||||
|
run: |
|
||||||
|
echo "🏗️ Building Windows executable..."
|
||||||
|
godot --headless --verbose --export-${{ needs.prepare.outputs.build_type }} "Windows Desktop" \
|
||||||
|
${{ env.BUILD_DIR }}/skelly-windows-${{ needs.prepare.outputs.version }}.exe
|
||||||
|
|
||||||
|
# Verify build output
|
||||||
|
if [[ -f "${{ env.BUILD_DIR }}/skelly-windows-${{ needs.prepare.outputs.version }}.exe" ]]; then
|
||||||
|
echo "✅ Windows build successful"
|
||||||
|
ls -la ${{ env.BUILD_DIR }}/
|
||||||
|
else
|
||||||
|
echo "❌ Windows build failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Windows build
|
||||||
|
uses: actions/upload-artifact@${{ env.ACTIONS_UPLOAD_ARTIFACT_VERSION }}
|
||||||
|
with:
|
||||||
|
name: ${{ needs.prepare.outputs.artifact_name }}-windows
|
||||||
|
path: ${{ env.BUILD_DIR }}/skelly-windows-${{ needs.prepare.outputs.version }}.exe
|
||||||
|
retention-days: 7
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
# Linux build job
|
||||||
|
build-linux:
|
||||||
|
name: Build Linux
|
||||||
|
runs-on: ${{ env.RUNNER_OS }}
|
||||||
|
needs: [prepare, setup-templates]
|
||||||
|
if: contains(needs.prepare.outputs.platforms, 'linux')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Godot
|
||||||
|
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||||
|
with:
|
||||||
|
version: ${{ env.GODOT_VERSION }}
|
||||||
|
use-dotnet: false
|
||||||
|
|
||||||
|
- name: Restore export templates cache
|
||||||
|
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||||
|
with:
|
||||||
|
path: ~/.local/share/godot/export_templates
|
||||||
|
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||||
|
restore-keys: |
|
||||||
|
godot-templates-
|
||||||
|
|
||||||
|
- name: Create build directory
|
||||||
|
run: mkdir -p ${{ env.BUILD_DIR }}
|
||||||
|
|
||||||
|
- name: Import project assets
|
||||||
|
run: |
|
||||||
|
echo "📦 Importing project assets..."
|
||||||
|
godot --headless --verbose --editor --quit || true
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
- name: Build Linux executable
|
||||||
|
run: |
|
||||||
|
echo "🏗️ Building Linux executable..."
|
||||||
|
godot --headless --verbose --export-${{ needs.prepare.outputs.build_type }} "Linux" \
|
||||||
|
${{ env.BUILD_DIR }}/skelly-linux-${{ needs.prepare.outputs.version }}.x86_64
|
||||||
|
|
||||||
|
# Make executable
|
||||||
|
chmod +x ${{ env.BUILD_DIR }}/skelly-linux-${{ needs.prepare.outputs.version }}.x86_64
|
||||||
|
|
||||||
|
# Verify build output
|
||||||
|
if [[ -f "${{ env.BUILD_DIR }}/skelly-linux-${{ needs.prepare.outputs.version }}.x86_64" ]]; then
|
||||||
|
echo "✅ Linux build successful"
|
||||||
|
ls -la ${{ env.BUILD_DIR }}/
|
||||||
|
else
|
||||||
|
echo "❌ Linux build failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Linux build
|
||||||
|
uses: actions/upload-artifact@${{ env.ACTIONS_UPLOAD_ARTIFACT_VERSION }}
|
||||||
|
with:
|
||||||
|
name: ${{ needs.prepare.outputs.artifact_name }}-linux
|
||||||
|
path: ${{ env.BUILD_DIR }}/skelly-linux-${{ needs.prepare.outputs.version }}.x86_64
|
||||||
|
retention-days: 7
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
# macOS build job
|
||||||
|
build-macos:
|
||||||
|
name: Build macOS
|
||||||
|
runs-on: ${{ env.RUNNER_OS }}
|
||||||
|
needs: [prepare, setup-templates]
|
||||||
|
if: contains(needs.prepare.outputs.platforms, 'macos')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Godot
|
||||||
|
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||||
|
with:
|
||||||
|
version: ${{ env.GODOT_VERSION }}
|
||||||
|
use-dotnet: false
|
||||||
|
|
||||||
|
- name: Restore export templates cache
|
||||||
|
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||||
|
with:
|
||||||
|
path: ~/.local/share/godot/export_templates
|
||||||
|
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||||
|
restore-keys: |
|
||||||
|
godot-templates-
|
||||||
|
|
||||||
|
- name: Create build directory
|
||||||
|
run: mkdir -p ${{ env.BUILD_DIR }}
|
||||||
|
|
||||||
|
- name: Import project assets
|
||||||
|
run: |
|
||||||
|
echo "📦 Importing project assets..."
|
||||||
|
godot --headless --verbose --editor --quit || true
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
- name: Build macOS application
|
||||||
|
run: |
|
||||||
|
echo "🏗️ Building macOS application..."
|
||||||
|
godot --headless --verbose --export-${{ needs.prepare.outputs.build_type }} "macOS" \
|
||||||
|
${{ env.BUILD_DIR }}/skelly-macos-${{ needs.prepare.outputs.version }}.zip
|
||||||
|
|
||||||
|
# Verify build output
|
||||||
|
if [[ -f "${{ env.BUILD_DIR }}/skelly-macos-${{ needs.prepare.outputs.version }}.zip" ]]; then
|
||||||
|
echo "✅ macOS build successful"
|
||||||
|
ls -la ${{ env.BUILD_DIR }}/
|
||||||
|
else
|
||||||
|
echo "❌ macOS build failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload macOS build
|
||||||
|
uses: actions/upload-artifact@${{ env.ACTIONS_UPLOAD_ARTIFACT_VERSION }}
|
||||||
|
with:
|
||||||
|
name: ${{ needs.prepare.outputs.artifact_name }}-macos
|
||||||
|
path: ${{ env.BUILD_DIR }}/skelly-macos-${{ needs.prepare.outputs.version }}.zip
|
||||||
|
retention-days: 7
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
# Android build job
|
||||||
|
build-android:
|
||||||
|
name: Build Android
|
||||||
|
runs-on: ${{ env.RUNNER_OS }}
|
||||||
|
needs: [prepare, setup-templates]
|
||||||
|
if: contains(needs.prepare.outputs.platforms, 'android')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@${{ env.ACTIONS_SETUP_JAVA_VERSION }}
|
||||||
|
with:
|
||||||
|
distribution: ${{ env.JAVA_DISTRIBUTION }}
|
||||||
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@${{ env.ANDROID_ACTIONS_SETUP_ANDROID_VERSION }}
|
||||||
|
with:
|
||||||
|
api-level: ${{ env.ANDROID_API_LEVEL }}
|
||||||
|
build-tools: ${{ env.ANDROID_BUILD_TOOLS_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Android Build Tools
|
||||||
|
run: |
|
||||||
|
echo "🔧 Installing Android Build Tools..."
|
||||||
|
|
||||||
|
# Set Android environment variables
|
||||||
|
export ANDROID_HOME=${ANDROID_SDK_ROOT}
|
||||||
|
echo "ANDROID_HOME=${ANDROID_SDK_ROOT}" >> $GITHUB_ENV
|
||||||
|
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Install build-tools using sdkmanager
|
||||||
|
yes | ${ANDROID_SDK_ROOT}/cmdline-tools/${{ env.ANDROID_CMDLINE_TOOLS_VERSION }}/bin/sdkmanager --licenses || true
|
||||||
|
${ANDROID_SDK_ROOT}/cmdline-tools/${{ env.ANDROID_CMDLINE_TOOLS_VERSION }}/bin/sdkmanager "build-tools;${{ env.ANDROID_BUILD_TOOLS_VERSION }}"
|
||||||
|
${ANDROID_SDK_ROOT}/cmdline-tools/${{ env.ANDROID_CMDLINE_TOOLS_VERSION }}/bin/sdkmanager "platforms;android-${{ env.ANDROID_API_LEVEL }}"
|
||||||
|
|
||||||
|
- name: Verify Android SDK Configuration
|
||||||
|
run: |
|
||||||
|
echo "📱 Verifying Android SDK setup..."
|
||||||
|
echo "📱 Using API Level: ${{ env.ANDROID_API_LEVEL }}"
|
||||||
|
echo "📱 Using Build Tools: ${{ env.ANDROID_BUILD_TOOLS_VERSION }}"
|
||||||
|
|
||||||
|
# Verify SDK installation
|
||||||
|
echo "📱 Android SDK Location: ${ANDROID_SDK_ROOT}"
|
||||||
|
ls -la ${ANDROID_SDK_ROOT}/
|
||||||
|
echo "📱 Build Tools:"
|
||||||
|
ls -la ${ANDROID_SDK_ROOT}/build-tools/
|
||||||
|
echo "📱 Platforms:"
|
||||||
|
ls -la ${ANDROID_SDK_ROOT}/platforms/ || echo "No platforms directory"
|
||||||
|
|
||||||
|
# Verify apksigner exists
|
||||||
|
if [ -f "${ANDROID_SDK_ROOT}/build-tools/${{ env.ANDROID_BUILD_TOOLS_VERSION }}/apksigner" ]; then
|
||||||
|
echo "✅ apksigner found at ${ANDROID_SDK_ROOT}/build-tools/${{ env.ANDROID_BUILD_TOOLS_VERSION }}/apksigner"
|
||||||
|
else
|
||||||
|
echo "❌ apksigner not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup Godot
|
||||||
|
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||||
|
with:
|
||||||
|
version: ${{ env.GODOT_VERSION }}
|
||||||
|
use-dotnet: false
|
||||||
|
|
||||||
|
- name: Configure Godot for Android
|
||||||
|
run: |
|
||||||
|
echo "🎮 Configuring Godot for Android builds..."
|
||||||
|
|
||||||
|
# Create Godot config directory
|
||||||
|
mkdir -p ~/.config/godot
|
||||||
|
|
||||||
|
# Configure Android SDK path in Godot settings
|
||||||
|
cat > ~/.config/godot/editor_settings-4.4.tres << EOF
|
||||||
|
[gd_resource type="EditorSettings" format=3]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
export/android/android_sdk_path = "${ANDROID_SDK_ROOT}"
|
||||||
|
export/android/debug_keystore = ""
|
||||||
|
export/android/debug_keystore_user = "androiddebugkey"
|
||||||
|
export/android/debug_keystore_pass = "android"
|
||||||
|
export/android/force_system_user = false
|
||||||
|
export/android/timestamping_authority_url = ""
|
||||||
|
export/android/shutdown_adb_on_exit = true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Godot Android configuration complete"
|
||||||
|
|
||||||
|
- name: Restore export templates cache
|
||||||
|
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||||
|
with:
|
||||||
|
path: ~/.local/share/godot/export_templates
|
||||||
|
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||||
|
restore-keys: |
|
||||||
|
godot-templates-
|
||||||
|
|
||||||
|
- name: Create build directory
|
||||||
|
run: mkdir -p ${{ env.BUILD_DIR }}
|
||||||
|
|
||||||
|
- name: Import project assets
|
||||||
|
run: |
|
||||||
|
echo "📦 Importing project assets..."
|
||||||
|
godot --headless --verbose --editor --quit || true
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
- name: Build Android APK
|
||||||
|
run: |
|
||||||
|
echo "🏗️ Building Android APK..."
|
||||||
|
|
||||||
|
# Verify Android environment
|
||||||
|
echo "📱 Android SDK: ${ANDROID_SDK_ROOT}"
|
||||||
|
echo "📱 API Level: ${{ env.ANDROID_API_LEVEL }}"
|
||||||
|
echo "📱 Build Tools Version: ${{ env.ANDROID_BUILD_TOOLS_VERSION }}"
|
||||||
|
echo "📱 Available Build Tools: $(ls ${ANDROID_SDK_ROOT}/build-tools/)"
|
||||||
|
|
||||||
|
godot --headless --verbose --export-${{ needs.prepare.outputs.build_type }} "Android" \
|
||||||
|
${{ env.BUILD_DIR }}/skelly-android-${{ needs.prepare.outputs.version }}.apk
|
||||||
|
|
||||||
|
# Verify build output
|
||||||
|
if [[ -f "${{ env.BUILD_DIR }}/skelly-android-${{ needs.prepare.outputs.version }}.apk" ]]; then
|
||||||
|
echo "✅ Android build successful"
|
||||||
|
ls -la ${{ env.BUILD_DIR }}/
|
||||||
|
|
||||||
|
# Show APK info
|
||||||
|
echo "📱 APK Information:"
|
||||||
|
file ${{ env.BUILD_DIR }}/skelly-android-${{ needs.prepare.outputs.version }}.apk
|
||||||
|
else
|
||||||
|
echo "❌ Android build failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Android build
|
||||||
|
uses: actions/upload-artifact@${{ env.ACTIONS_UPLOAD_ARTIFACT_VERSION }}
|
||||||
|
with:
|
||||||
|
name: ${{ needs.prepare.outputs.artifact_name }}-android
|
||||||
|
path: ${{ env.BUILD_DIR }}/skelly-android-${{ needs.prepare.outputs.version }}.apk
|
||||||
|
retention-days: 7
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
# Summary job - creates release summary
|
||||||
|
summary:
|
||||||
|
name: Build Summary
|
||||||
|
runs-on: ${{ env.RUNNER_OS }}
|
||||||
|
needs: [prepare, setup-templates, build-windows, build-linux, build-macos, build-android]
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Generate build summary
|
||||||
|
run: |
|
||||||
|
echo "🎮 Build Summary for ${{ needs.prepare.outputs.artifact_name }}"
|
||||||
|
echo "=================================="
|
||||||
|
echo ""
|
||||||
|
echo "📋 Configuration:"
|
||||||
|
echo " Version: ${{ needs.prepare.outputs.version }}"
|
||||||
|
echo " Build Type: ${{ needs.prepare.outputs.build_type }}"
|
||||||
|
echo " Platforms: ${{ needs.prepare.outputs.platforms }}"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Build Results:"
|
||||||
|
|
||||||
|
platforms="${{ needs.prepare.outputs.platforms }}"
|
||||||
|
|
||||||
|
if [[ "$platforms" == *"windows"* ]]; then
|
||||||
|
windows_status="${{ needs.build-windows.result }}"
|
||||||
|
echo " 🪟 Windows: $windows_status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$platforms" == *"linux"* ]]; then
|
||||||
|
linux_status="${{ needs.build-linux.result }}"
|
||||||
|
echo " 🐧 Linux: $linux_status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$platforms" == *"macos"* ]]; then
|
||||||
|
macos_status="${{ needs.build-macos.result }}"
|
||||||
|
echo " 🍎 macOS: $macos_status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$platforms" == *"android"* ]]; then
|
||||||
|
android_status="${{ needs.build-android.result }}"
|
||||||
|
echo " 🤖 Android: $android_status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📦 Artifacts are available for 7 days"
|
||||||
|
echo "🔗 Download from: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
|
||||||
|
- name: Check overall build status
|
||||||
|
run: |
|
||||||
|
# Check if any required builds failed
|
||||||
|
platforms="${{ needs.prepare.outputs.platforms }}"
|
||||||
|
failed_builds=()
|
||||||
|
|
||||||
|
if [[ "$platforms" == *"windows"* ]] && [[ "${{ needs.build-windows.result }}" != "success" ]]; then
|
||||||
|
failed_builds+=("Windows")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$platforms" == *"linux"* ]] && [[ "${{ needs.build-linux.result }}" != "success" ]]; then
|
||||||
|
failed_builds+=("Linux")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$platforms" == *"macos"* ]] && [[ "${{ needs.build-macos.result }}" != "success" ]]; then
|
||||||
|
failed_builds+=("macOS")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$platforms" == *"android"* ]] && [[ "${{ needs.build-android.result }}" != "success" ]]; then
|
||||||
|
failed_builds+=("Android")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#failed_builds[@]} -gt 0 ]]; then
|
||||||
|
echo "❌ Build failed for: ${failed_builds[*]}"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "✅ All builds completed successfully!"
|
||||||
|
fi
|
||||||
357
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
# CI pipeline for the Skelly Godot project
|
||||||
|
#
|
||||||
|
# Code quality checks (formatting, linting, testing) run as independent jobs
|
||||||
|
# in parallel. Uses tools/run_development.py for consistency with local development.
|
||||||
|
#
|
||||||
|
# Features:
|
||||||
|
# - Independent job execution (no dependencies between format/lint/test)
|
||||||
|
# - Automatic code formatting with commit back to branch
|
||||||
|
# - Error reporting and PR comments
|
||||||
|
# - Manual execution with selective step skipping
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Trigger on push to any branch - only when relevant files change
|
||||||
|
push:
|
||||||
|
branches: ['*']
|
||||||
|
paths:
|
||||||
|
- '**/*.gd' # Any GDScript file
|
||||||
|
- '.gdlintrc' # Linting configuration
|
||||||
|
- '.gdformatrc' # Formatting configuration
|
||||||
|
- 'tools/run_development.py' # Development workflow script
|
||||||
|
- '.gitea/workflows/ci.yml' # This workflow file
|
||||||
|
|
||||||
|
# Trigger on pull requests - same file filters as push
|
||||||
|
pull_request:
|
||||||
|
branches: ['*']
|
||||||
|
paths:
|
||||||
|
- '**/*.gd'
|
||||||
|
- '.gdlintrc'
|
||||||
|
- '.gdformatrc'
|
||||||
|
- 'tools/run_development.py'
|
||||||
|
- '.gitea/workflows/ci.yml'
|
||||||
|
|
||||||
|
# Allow manual triggering with optional step skipping
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
skip_format:
|
||||||
|
description: 'Skip code formatting'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: boolean
|
||||||
|
skip_lint:
|
||||||
|
description: 'Skip code linting'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: boolean
|
||||||
|
skip_tests:
|
||||||
|
description: 'Skip test execution'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
format:
|
||||||
|
name: Code Formatting
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
if: ${{ always() && github.event.inputs.skip_format != 'true' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.ref || github.ref }}
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade "setuptools<81"
|
||||||
|
pip install gdtoolkit==4
|
||||||
|
|
||||||
|
- name: Run code formatting
|
||||||
|
id: format
|
||||||
|
run: |
|
||||||
|
echo "🎨 Running GDScript formatting..."
|
||||||
|
python tools/run_development.py --format --silent --yaml > format_results.yaml
|
||||||
|
|
||||||
|
- name: Upload formatting results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: format-results
|
||||||
|
path: |
|
||||||
|
format_results.yaml
|
||||||
|
retention-days: 7
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
- name: Check for formatting changes
|
||||||
|
id: check-changes
|
||||||
|
run: |
|
||||||
|
if git diff --quiet; then
|
||||||
|
echo "📝 No formatting changes detected"
|
||||||
|
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "📝 Formatting changes detected"
|
||||||
|
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "🔍 Changed files:"
|
||||||
|
git diff --name-only
|
||||||
|
echo ""
|
||||||
|
echo "📊 Diff summary:"
|
||||||
|
git diff --stat
|
||||||
|
fi
|
||||||
|
|
||||||
|
# - name: Commit and push formatting changes
|
||||||
|
# if: steps.check-changes.outputs.has_changes == 'true'
|
||||||
|
# run: |
|
||||||
|
# echo "💾 Committing formatting changes..."
|
||||||
|
|
||||||
|
# git config user.name "Gitea Actions"
|
||||||
|
# git config user.email "actions@gitea.local"
|
||||||
|
|
||||||
|
# git add -A
|
||||||
|
|
||||||
|
# commit_message="🎨 Auto-format GDScript code
|
||||||
|
|
||||||
|
# Automated formatting applied by tools/run_development.py
|
||||||
|
|
||||||
|
# 🤖 Generated by Gitea Actions
|
||||||
|
# Workflow: ${{ github.workflow }}
|
||||||
|
# Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
|
||||||
|
# git commit -m "$commit_message"
|
||||||
|
|
||||||
|
# target_branch="${{ github.event.pull_request.head.ref || github.ref_name }}"
|
||||||
|
# echo "📤 Pushing changes to branch: $target_branch"
|
||||||
|
# git push origin HEAD:"$target_branch"
|
||||||
|
|
||||||
|
# echo "✅ Formatting changes pushed successfully!"
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Code Quality Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.inputs.skip_lint != 'true' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.ref || github.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade "setuptools<81"
|
||||||
|
pip install gdtoolkit==4
|
||||||
|
|
||||||
|
- name: Run linting
|
||||||
|
id: lint
|
||||||
|
run: |
|
||||||
|
echo "🔍 Running GDScript linting..."
|
||||||
|
python tools/run_development.py --lint --silent --yaml > lint_results.yaml
|
||||||
|
|
||||||
|
- name: Upload linting results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: lint-results
|
||||||
|
path: |
|
||||||
|
lint_results.yaml
|
||||||
|
retention-days: 7
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test Execution
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.inputs.skip_tests != 'true' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.ref || github.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade "setuptools<81"
|
||||||
|
pip install gdtoolkit==4
|
||||||
|
|
||||||
|
- name: Set up Godot
|
||||||
|
uses: chickensoft-games/setup-godot@v1
|
||||||
|
with:
|
||||||
|
version: 4.3.0
|
||||||
|
use-dotnet: false
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
id: test
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
echo "🧪 Running GDScript tests..."
|
||||||
|
python tools/run_development.py --test --yaml > test_results.yaml 2>&1
|
||||||
|
TEST_EXIT_CODE=$?
|
||||||
|
|
||||||
|
# Display test results regardless of success/failure
|
||||||
|
echo ""
|
||||||
|
echo "📊 Test Results:"
|
||||||
|
cat test_results.yaml
|
||||||
|
|
||||||
|
# Exit with the original test exit code
|
||||||
|
exit $TEST_EXIT_CODE
|
||||||
|
|
||||||
|
- name: Upload test results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: test-results
|
||||||
|
path: |
|
||||||
|
test_results.yaml
|
||||||
|
retention-days: 7
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
- name: Check test results and display errors
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo ""
|
||||||
|
echo "================================================"
|
||||||
|
echo "📊 TEST RESULTS SUMMARY"
|
||||||
|
echo "================================================"
|
||||||
|
|
||||||
|
# Parse YAML to check if tests failed
|
||||||
|
if grep -q "success: false" test_results.yaml; then
|
||||||
|
echo "❌ Status: FAILED"
|
||||||
|
echo ""
|
||||||
|
echo "💥 Failed Test Details:"
|
||||||
|
echo "================================================"
|
||||||
|
|
||||||
|
# Extract and display failed test information
|
||||||
|
grep -A 2 "failed_test_details:" test_results.yaml || echo "No detailed error information available"
|
||||||
|
|
||||||
|
# Show statistics
|
||||||
|
echo ""
|
||||||
|
echo "📈 Statistics:"
|
||||||
|
grep "tests_passed:" test_results.yaml || true
|
||||||
|
grep "tests_failed:" test_results.yaml || true
|
||||||
|
grep "total_tests_run:" test_results.yaml || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "================================================"
|
||||||
|
echo "⚠️ Please review the test errors above and fix them before merging."
|
||||||
|
echo "================================================"
|
||||||
|
exit 1
|
||||||
|
elif grep -q "success: true" test_results.yaml; then
|
||||||
|
echo "✅ Status: PASSED"
|
||||||
|
echo ""
|
||||||
|
grep "total_tests_run:" test_results.yaml || true
|
||||||
|
echo "================================================"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "⚠️ Status: UNKNOWN"
|
||||||
|
echo "Could not determine test status from YAML output"
|
||||||
|
echo "================================================"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
summary:
|
||||||
|
name: CI Summary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [format, lint, test]
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set workflow status
|
||||||
|
id: status
|
||||||
|
run: |
|
||||||
|
format_status="${{ needs.format.result }}"
|
||||||
|
lint_status="${{ needs.lint.result }}"
|
||||||
|
test_status="${{ needs.test.result }}"
|
||||||
|
|
||||||
|
echo "📊 Workflow Results:"
|
||||||
|
echo "🎨 Format: $format_status"
|
||||||
|
echo "🔍 Lint: $lint_status"
|
||||||
|
echo "🧪 Test: $test_status"
|
||||||
|
|
||||||
|
if [[ "$format_status" == "success" && "$lint_status" == "success" && ("$test_status" == "success" || "$test_status" == "skipped") ]]; then
|
||||||
|
echo "overall_status=success" >> $GITHUB_OUTPUT
|
||||||
|
echo "✅ All CI checks passed!"
|
||||||
|
else
|
||||||
|
echo "overall_status=failure" >> $GITHUB_OUTPUT
|
||||||
|
echo "❌ Some CI checks failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Comment on PR (if applicable)
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const formatStatus = '${{ needs.format.result }}';
|
||||||
|
const lintStatus = '${{ needs.lint.result }}';
|
||||||
|
const testStatus = '${{ needs.test.result }}';
|
||||||
|
const overallStatus = '${{ steps.status.outputs.overall_status }}';
|
||||||
|
|
||||||
|
const getStatusEmoji = (status) => {
|
||||||
|
switch(status) {
|
||||||
|
case 'success': return '✅';
|
||||||
|
case 'failure': return '❌';
|
||||||
|
case 'skipped': return '⏭️';
|
||||||
|
default: return '⚠️';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const message = `## 🤖 CI Pipeline Results
|
||||||
|
|
||||||
|
| Step | Status | Result |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 🎨 Formatting | ${getStatusEmoji(formatStatus)} | ${formatStatus} |
|
||||||
|
| 🔍 Linting | ${getStatusEmoji(lintStatus)} | ${lintStatus} |
|
||||||
|
| 🧪 Testing | ${getStatusEmoji(testStatus)} | ${testStatus} |
|
||||||
|
|
||||||
|
**Overall Status:** ${getStatusEmoji(overallStatus)} ${overallStatus.toUpperCase()}
|
||||||
|
|
||||||
|
${overallStatus === 'success'
|
||||||
|
? '🎉 All checks passed! This PR is ready for review.'
|
||||||
|
: '⚠️ Some checks failed. Please review the workflow logs and fix any issues.'}
|
||||||
|
|
||||||
|
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
|
||||||
|
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: message
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Set final exit code
|
||||||
|
run: |
|
||||||
|
if [[ "${{ steps.status.outputs.overall_status }}" == "success" ]]; then
|
||||||
|
echo "🎉 CI Pipeline completed successfully!"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ CI Pipeline failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
15
.gitignore
vendored
@@ -1,3 +1,18 @@
|
|||||||
# Godot 4+ specific ignores
|
# Godot 4+ specific ignores
|
||||||
.godot/
|
.godot/
|
||||||
/android/
|
/android/
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
*.tmp
|
||||||
|
*.import~
|
||||||
|
test_results.txt
|
||||||
|
|
||||||
|
# Auto-generated code maps (regenerate with: python tools/generate_code_map.py)
|
||||||
|
.llm/
|
||||||
|
docs/generated/
|
||||||
|
|
||||||
|
# python
|
||||||
|
|
||||||
|
.venv/
|
||||||
|
*.pyc
|
||||||
|
.ruff/
|
||||||
|
|||||||
32
.llm/README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Machine-Readable Code Maps
|
||||||
|
|
||||||
|
Auto-generated JSON files for LLM development assistance.
|
||||||
|
|
||||||
|
**⚠️ Git-ignored** - Regenerate with: `python tools/generate_code_map.py`
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `code_map_api.json` - Function signatures, parameters, return types, docstrings
|
||||||
|
- `code_map_architecture.json` - Autoloads, design patterns, system structure
|
||||||
|
- `code_map_flows.json` - Signal chains, scene transitions, call graphs
|
||||||
|
- `code_map_security.json` - Input validation, error handling patterns
|
||||||
|
- `code_map_assets.json` - Asset dependencies, licensing information
|
||||||
|
- `code_map_metadata.json` - Code quality metrics, test coverage, TODOs
|
||||||
|
|
||||||
|
## Diagrams
|
||||||
|
|
||||||
|
**Location**: `diagrams/` subdirectory (also copied to `docs/generated/diagrams/` for standalone viewing)
|
||||||
|
|
||||||
|
**Contents**:
|
||||||
|
- `*.mmd` - Mermaid source files (architecture, signal flows, scene hierarchy, dependencies)
|
||||||
|
- `*.png` - Rendered PNG diagrams
|
||||||
|
|
||||||
|
**Note**: Diagrams are automatically copied to `docs/generated/diagrams/` when generating documentation.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
|
All JSON files are **minified** (no whitespace) for optimal LLM token efficiency.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
See root `CLAUDE.md` for integration with LLM development workflows.
|
||||||
224
CLAUDE.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
Guidance for Claude Code (claude.ai/code) when working with this repository.
|
||||||
|
|
||||||
|
## Required Context Files
|
||||||
|
|
||||||
|
Before doing anything else, get the following files in context:
|
||||||
|
- **docs/ARCHITECTURE.md** - System design, autoloads, architectural patterns
|
||||||
|
- **docs/CODE_OF_CONDUCT.md** - Coding standards, naming conventions, best practices
|
||||||
|
- **project.godot** - Input actions and autoload definitions
|
||||||
|
|
||||||
|
## Auto-Generated Machine-Readable Documentation
|
||||||
|
|
||||||
|
**⚠️ IMPORTANT**: Before starting development, generate fresh code maps for current codebase context:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python tools/generate_code_map.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This generates machine-readable JSON maps optimized for LLM consumption in `.llm/` directory (git-ignored).
|
||||||
|
|
||||||
|
**When to use**:
|
||||||
|
- At the start of every development session
|
||||||
|
- After major refactoring or adding new files
|
||||||
|
- When you need current codebase structure/API reference
|
||||||
|
|
||||||
|
## Core Development Rules
|
||||||
|
|
||||||
|
- **Use TDD methodology** for development
|
||||||
|
- **Use static data types** in all GDScript code
|
||||||
|
- **Keep documentation up to date** when making changes
|
||||||
|
- **Always run tests**: `./tools/run_development.py --yaml --silent`
|
||||||
|
|
||||||
|
## Code Maps (LLM Development Workflow)
|
||||||
|
|
||||||
|
Machine-readable code intelligence for LLM-assisted development.
|
||||||
|
|
||||||
|
### Quick Reference: Which Map to Load?
|
||||||
|
|
||||||
|
| Task | Load This Map | Size | Contains |
|
||||||
|
|------|---------------|------|----------|
|
||||||
|
| Adding/modifying functions | `code_map_api.json` | ~47KB | Function signatures, parameters, return types |
|
||||||
|
| System architecture work | `code_map_architecture.json` | ~32KB | Autoloads, design patterns, structure |
|
||||||
|
| Signal/scene connections | `code_map_flows.json` | ~7KB | Signal chains, scene transitions |
|
||||||
|
| Input validation/errors | `code_map_security.json` | ~12KB | Validation patterns, error handling |
|
||||||
|
| Asset/resource management | `code_map_assets.json` | ~12KB | Dependencies, preloads, licensing |
|
||||||
|
| Code metrics/statistics | `code_map_metadata.json` | ~300B | Stats, complexity metrics |
|
||||||
|
|
||||||
|
### Generated Files (Git-Ignored)
|
||||||
|
|
||||||
|
**Location**: `.llm/` directory (automatically excluded from git)
|
||||||
|
|
||||||
|
**Format**: All JSON files are minified (no whitespace) for optimal LLM token efficiency.
|
||||||
|
|
||||||
|
**Total size**: ~110KB if loading all maps (recommended: load only what you need)
|
||||||
|
|
||||||
|
### Generating Code Maps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate everything (default behavior - maps + diagrams + docs + metrics)
|
||||||
|
python tools/generate_code_map.py
|
||||||
|
|
||||||
|
# Generate only specific outputs
|
||||||
|
python tools/generate_code_map.py --diagrams # Diagrams only
|
||||||
|
python tools/generate_code_map.py --docs # Documentation only
|
||||||
|
python tools/generate_code_map.py --metrics # Metrics dashboard only
|
||||||
|
|
||||||
|
# Explicitly generate all (same as no arguments)
|
||||||
|
python tools/generate_code_map.py --all
|
||||||
|
|
||||||
|
# Via development workflow tool
|
||||||
|
python tools/run_development.py --codemap
|
||||||
|
|
||||||
|
# Custom output location (single JSON file)
|
||||||
|
python tools/generate_code_map.py --output custom_map.json
|
||||||
|
|
||||||
|
# Skip PNG rendering (Mermaid source only)
|
||||||
|
python tools/generate_code_map.py --diagrams --no-render
|
||||||
|
```
|
||||||
|
|
||||||
|
**New Features**:
|
||||||
|
- **Diagrams**: Auto-generated diagrams (architecture, signal flows, scene hierarchy, dependencies) rendered to PNG using matplotlib
|
||||||
|
- **Documentation**: Human-readable markdown docs with embedded diagrams (AUTOLOADS_API.md, SIGNALS_CATALOG.md, FUNCTION_INDEX.md, etc.)
|
||||||
|
- **Metrics**: Markdown dashboard with statistics charts and complexity analysis
|
||||||
|
|
||||||
|
### When to Regenerate
|
||||||
|
|
||||||
|
Regenerate code maps when:
|
||||||
|
- **Starting an LLM-assisted development session** - Get fresh context
|
||||||
|
- **After adding new files** - Include new code in maps
|
||||||
|
- **After major refactoring** - Update structure information
|
||||||
|
- **After changing autoloads** - Capture architectural changes
|
||||||
|
- **When onboarding new developers** - Provide comprehensive context
|
||||||
|
|
||||||
|
### LLM Development Workflow
|
||||||
|
|
||||||
|
1. **Generate maps**: `python tools/generate_code_map.py`
|
||||||
|
|
||||||
|
2. **Load relevant maps**: Choose based on your task
|
||||||
|
|
||||||
|
**For API/Function development**:
|
||||||
|
```
|
||||||
|
Load: .llm/code_map_api.json (~47KB)
|
||||||
|
Contains: All function signatures, parameters, return types, docstrings
|
||||||
|
```
|
||||||
|
|
||||||
|
**For architecture/system design**:
|
||||||
|
```
|
||||||
|
Load: .llm/code_map_architecture.json (~32KB)
|
||||||
|
Contains: Autoloads, design patterns, system structure
|
||||||
|
```
|
||||||
|
|
||||||
|
**For signal/scene work**:
|
||||||
|
```
|
||||||
|
Load: .llm/code_map_flows.json (~7KB)
|
||||||
|
Contains: Signal chains, scene transitions, connections
|
||||||
|
```
|
||||||
|
|
||||||
|
**For security/validation**:
|
||||||
|
```
|
||||||
|
Load: .llm/code_map_security.json (~12KB)
|
||||||
|
Contains: Input validation patterns, error handling
|
||||||
|
```
|
||||||
|
|
||||||
|
**For assets/resources**:
|
||||||
|
```
|
||||||
|
Load: .llm/code_map_assets.json (~12KB)
|
||||||
|
Contains: Asset dependencies, preloads, licensing
|
||||||
|
```
|
||||||
|
|
||||||
|
**For metrics/stats**:
|
||||||
|
```
|
||||||
|
Load: .llm/code_map_metadata.json (~300B)
|
||||||
|
Contains: Code statistics, complexity metrics
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Use selectively**:
|
||||||
|
- Load only maps relevant to current task
|
||||||
|
- Reduces token usage and improves focus
|
||||||
|
- Can always add more context later
|
||||||
|
- Total size if loading all: ~110KB minified JSON
|
||||||
|
|
||||||
|
## Essential Coding Patterns
|
||||||
|
|
||||||
|
### Scene Management
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct - Use GameManager
|
||||||
|
GameManager.start_game_with_mode("match3")
|
||||||
|
|
||||||
|
# ❌ Wrong - Never call directly
|
||||||
|
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct - Use DebugManager with categories
|
||||||
|
DebugManager.log_info("Scene loaded successfully", "GameManager")
|
||||||
|
DebugManager.log_error("Failed to load resource", "AssetLoader")
|
||||||
|
|
||||||
|
# ❌ Wrong - Never use plain print/push_error
|
||||||
|
print("Scene loaded") # No structure, no category
|
||||||
|
push_error("Failed") # Missing context
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Management
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct - Use queue_free()
|
||||||
|
for child in children_to_remove:
|
||||||
|
child.queue_free()
|
||||||
|
await get_tree().process_frame # Wait for cleanup
|
||||||
|
|
||||||
|
# ❌ Wrong - Never use free()
|
||||||
|
child.free() # Immediate deletion causes crashes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Input Validation
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct - Validate all inputs
|
||||||
|
func set_volume(value: float) -> bool:
|
||||||
|
if value < 0.0 or value > 1.0:
|
||||||
|
DebugManager.log_error("Invalid volume: " + str(value), "Settings")
|
||||||
|
return false
|
||||||
|
# Process validated input
|
||||||
|
|
||||||
|
# ❌ Wrong - No validation
|
||||||
|
func set_volume(value: float):
|
||||||
|
volume = value # Could be negative or > 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md) for complete coding standards.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
- **Development commands**: See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)
|
||||||
|
- **Testing protocols**: See [docs/TESTING.md](docs/TESTING.md)
|
||||||
|
- **Architecture patterns**: See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
||||||
|
- **Naming conventions**: See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md#naming-convention-quick-reference)
|
||||||
|
- **Quality checklist**: See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md#code-quality-checklist)
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
skelly/
|
||||||
|
├── src/autoloads/ # Global manager singletons
|
||||||
|
├── scenes/game/gameplays/ # Gameplay mode implementations
|
||||||
|
├── scenes/ui/ # Menu scenes and components
|
||||||
|
├── assets/ # Audio, sprites, fonts (kebab-case)
|
||||||
|
├── data/ # Godot resource files (.tres)
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── tests/ # Test scripts
|
||||||
|
└── tools/ # Development tools
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/MAP.md](docs/MAP.md) for complete file organization.
|
||||||
|
|
||||||
|
## Key Files to Understand
|
||||||
|
|
||||||
|
- `src/autoloads/GameManager.gd` - Scene transitions with race condition protection
|
||||||
|
- `src/autoloads/SaveManager.gd` - Save system with security features
|
||||||
|
- `src/autoloads/SettingsManager.gd` - Settings with input validation
|
||||||
|
- `src/autoloads/DebugManager.gd` - Unified logging and debug UI
|
||||||
|
- `scenes/game/Game.gd` - Main game scene, modular gameplay system
|
||||||
|
- `scenes/game/gameplays/Match3Gameplay.gd` - Match-3 implementation
|
||||||
|
- `project.godot` - Input actions and autoload definitions
|
||||||
97
DEVELOPMENT_TOOLS.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# Development Tools
|
||||||
|
|
||||||
|
Development workflow tools for the Skelly Godot project.
|
||||||
|
|
||||||
|
Python script that handles code formatting, linting, and testing.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Run all development checks (recommended for pre-commit):
|
||||||
|
```bash
|
||||||
|
run_dev.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs code formatting → linting → testing.
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
### Main Unified Script
|
||||||
|
- **`run_dev.bat`** - Main unified development script with all functionality
|
||||||
|
|
||||||
|
### Individual Tools (Legacy - redirect to unified script)
|
||||||
|
- **`run_all.bat`** - Same as `run_dev.bat` (legacy compatibility)
|
||||||
|
- **`run_lint.bat`** - Run only linting (redirects to `run_dev.bat --lint`)
|
||||||
|
- **`run_format.bat`** - Run only formatting (redirects to `run_dev.bat --format`)
|
||||||
|
- **`run_tests.bat`** - Run only tests (redirects to `run_dev.bat --test`)
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all checks (default behavior)
|
||||||
|
run_dev.bat
|
||||||
|
|
||||||
|
# Run only specific tools
|
||||||
|
run_dev.bat --lint
|
||||||
|
run_dev.bat --format
|
||||||
|
run_dev.bat --test
|
||||||
|
|
||||||
|
# Run custom workflow steps
|
||||||
|
run_dev.bat --steps format lint
|
||||||
|
run_dev.bat --steps format test
|
||||||
|
|
||||||
|
# Show help
|
||||||
|
run_dev.bat --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Each Tool Does
|
||||||
|
|
||||||
|
### 🔍 Linting (`gdlint`)
|
||||||
|
- Checks GDScript code for style violations
|
||||||
|
- Enforces naming conventions
|
||||||
|
- Validates code structure and patterns
|
||||||
|
- **Fails the workflow if errors are found**
|
||||||
|
|
||||||
|
### 🎨 Formatting (`gdformat`)
|
||||||
|
- Automatically formats GDScript code
|
||||||
|
- Ensures consistent indentation and spacing
|
||||||
|
- Fixes basic style issues
|
||||||
|
- **Fails the workflow if files cannot be formatted**
|
||||||
|
|
||||||
|
### 🧪 Testing (`godot`)
|
||||||
|
- Runs all test files in `tests/` directory
|
||||||
|
- Executes Godot scripts in headless mode
|
||||||
|
- Reports test results and failures
|
||||||
|
- **Continues workflow even if tests fail** (for review)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
The script automatically checks for and provides installation instructions for:
|
||||||
|
- Python 3.x
|
||||||
|
- pip
|
||||||
|
- Godot Engine (for tests)
|
||||||
|
- gdtoolkit (gdlint, gdformat)
|
||||||
|
|
||||||
|
## Output Features
|
||||||
|
|
||||||
|
- Colorized output
|
||||||
|
- Emoji status indicators
|
||||||
|
- Tool summaries
|
||||||
|
- Execution time tracking
|
||||||
|
- Warning suppression
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. **Before committing**: Run `run_dev.bat` to ensure code quality
|
||||||
|
2. **Fix any linting errors** - the workflow will abort on errors
|
||||||
|
3. **Review any test failures** - tests don't abort workflow but should be addressed
|
||||||
|
4. **Commit your changes** once all checks pass
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
- Git hooks (pre-commit)
|
||||||
|
- CI/CD pipelines
|
||||||
|
- IDE integrations
|
||||||
|
- Manual development workflow
|
||||||
|
|
||||||
|
Legacy batch files remain functional.
|
||||||
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) 2025 Vladimir @nett00n Budylnikov
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) 2025 Vladimir @nett00n Budylnikov
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
147
README.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Skelly
|
||||||
|
|
||||||
|
Godot 4.4 mobile game project with multiple gameplay modes. Features modular gameplay system with match-3 puzzle (implemented) and planned clickomania mode.
|
||||||
|
|
||||||
|
**Tech Stack**: Godot 4.4, GDScript
|
||||||
|
**Target Platform**: Mobile (Android/iOS)
|
||||||
|
**Architecture**: See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for detailed system design
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Running the Project
|
||||||
|
|
||||||
|
1. **Open in Godot Editor**: Import `project.godot`
|
||||||
|
2. **Run**: Press `F5` or click the "Play" button
|
||||||
|
3. **Debug UI**: Press `F12` in-game to toggle debug panels
|
||||||
|
|
||||||
|
### Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate code maps for LLM development
|
||||||
|
python tools/generate_code_map.py
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
godot --headless --script tests/TestLogging.gd
|
||||||
|
|
||||||
|
# Development workflow helper
|
||||||
|
python tools/run_development.py --codemap # Generate code maps
|
||||||
|
python tools/run_development.py --test # Run tests
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for complete development workflows and commands.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
skelly/
|
||||||
|
├── src/autoloads/ # Global manager singletons (GameManager, SaveManager, etc.)
|
||||||
|
├── scenes/
|
||||||
|
│ ├── game/gameplays/ # Gameplay mode implementations (Match3, Clickomania)
|
||||||
|
│ ├── ui/components/ # Reusable UI components (ValueStepper, etc.)
|
||||||
|
│ └── ui/ # Menu scenes (MainMenu, SettingsMenu)
|
||||||
|
├── assets/ # Audio, sprites, fonts (kebab-case naming)
|
||||||
|
├── data/ # Godot resource files (.tres)
|
||||||
|
├── docs/ # Documentation (architecture, coding standards, testing)
|
||||||
|
├── tests/ # Test scripts for system validation
|
||||||
|
├── tools/ # Development tools (code map generator, etc.)
|
||||||
|
└── localization/ # Translation files (English, Russian)
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/MAP.md](docs/MAP.md) for complete file organization details.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** - System design, autoloads, architectural patterns
|
||||||
|
- **[docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md)** - Coding standards, naming conventions, best practices
|
||||||
|
- **[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)** - Development workflows and commands
|
||||||
|
- **[docs/TESTING.md](docs/TESTING.md)** - Testing procedures and protocols
|
||||||
|
- **[docs/UI_COMPONENTS.md](docs/UI_COMPONENTS.md)** - UI component API reference
|
||||||
|
- **[docs/MAP.md](docs/MAP.md)** - Complete project structure
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Modular Gameplay System**: Easy to add new game modes
|
||||||
|
- **Match-3 Puzzle**: Fully implemented with keyboard/gamepad support
|
||||||
|
- **Settings Management**: Volume, language, difficulty with input validation
|
||||||
|
- **Save System**: Secure save/load with tamper detection and auto-repair
|
||||||
|
- **Debug System**: F12 toggle for debug panels on all major scenes
|
||||||
|
- **Localization**: Multi-language support (English, Russian)
|
||||||
|
- **Audio System**: Music and SFX with bus-based volume control
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Before Making Changes
|
||||||
|
|
||||||
|
1. Read [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md) for coding standards
|
||||||
|
2. Review [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for system design
|
||||||
|
3. Check [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for workflows
|
||||||
|
4. Follow the [quality checklist](docs/CODE_OF_CONDUCT.md#code-quality-checklist)
|
||||||
|
|
||||||
|
### Core Development Rules
|
||||||
|
|
||||||
|
- **Scene transitions**: Use `GameManager.start_game_with_mode()` - never call `get_tree().change_scene_to_file()` directly
|
||||||
|
- **Logging**: Use `DebugManager.log_*()` functions with categories - never use plain `print()` or `push_error()`
|
||||||
|
- **Memory management**: Always use `queue_free()` instead of `free()`
|
||||||
|
- **Input validation**: Validate all user inputs with type/bounds/null checking
|
||||||
|
- **TDD methodology**: Write tests for new functionality
|
||||||
|
|
||||||
|
See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md) for complete standards.
|
||||||
|
|
||||||
|
### Asset Management
|
||||||
|
|
||||||
|
- **Document every asset** in `assets/sources.yaml` before committing
|
||||||
|
- Include source URL, license, attribution, modifications, and usage
|
||||||
|
- Verify license compatibility (CC BY, CC0, Public Domain, etc.)
|
||||||
|
- Commit asset files and sources.yaml together
|
||||||
|
|
||||||
|
See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md#asset-management) for detailed workflow.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manual testing
|
||||||
|
# F5 in Godot Editor - Run project
|
||||||
|
# F12 in-game - Toggle debug UI
|
||||||
|
|
||||||
|
# Automated testing
|
||||||
|
godot --headless --script tests/TestLogging.gd
|
||||||
|
godot --headless --script tests/test_checksum_issue.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/TESTING.md](docs/TESTING.md) for complete testing procedures.
|
||||||
|
|
||||||
|
## Development Highlights
|
||||||
|
|
||||||
|
### Autoload System
|
||||||
|
|
||||||
|
Global singleton services providing core functionality:
|
||||||
|
- **GameManager**: Scene transitions with race condition protection
|
||||||
|
- **SaveManager**: Secure save/load with tamper detection
|
||||||
|
- **SettingsManager**: Settings persistence with input validation
|
||||||
|
- **DebugManager**: Unified logging and debug UI
|
||||||
|
- **AudioManager**: Music and SFX playback
|
||||||
|
- **LocalizationManager**: Language switching
|
||||||
|
|
||||||
|
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#autoload-system) for details.
|
||||||
|
|
||||||
|
### Code Quality Standards
|
||||||
|
|
||||||
|
- **Memory Safety**: `queue_free()` pattern for safe node cleanup
|
||||||
|
- **Error Handling**: Comprehensive error handling with fallbacks
|
||||||
|
- **Race Condition Protection**: State flags for async operations
|
||||||
|
- **Instance-Based Architecture**: No global static state
|
||||||
|
- **Input Validation**: Complete validation coverage
|
||||||
|
|
||||||
|
See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md#code-quality-checklist) for complete quality standards.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Godot GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html)
|
||||||
|
- [Godot Best Practices](https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html)
|
||||||
|
- Project documentation in `docs/` directory
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GPLv3.0
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) file for more information
|
||||||
24
assets/audio/music/817587__silverdubloons__tick06.wav.import
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="wav"
|
||||||
|
type="AudioStreamWAV"
|
||||||
|
uid="uid://briuh6uhf0tdt"
|
||||||
|
path="res://.godot/imported/817587__silverdubloons__tick06.wav-6928d7a957ad4e7ff0ddda00a7348675.sample"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://audio/817587__silverdubloons__tick06.wav"
|
||||||
|
dest_files=["res://.godot/imported/817587__silverdubloons__tick06.wav-6928d7a957ad4e7ff0ddda00a7348675.sample"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
force/8_bit=false
|
||||||
|
force/mono=false
|
||||||
|
force/max_rate=false
|
||||||
|
force/max_rate_hz=44100
|
||||||
|
edit/trim=false
|
||||||
|
edit/normalize=false
|
||||||
|
edit/loop_mode=0
|
||||||
|
edit/loop_begin=0
|
||||||
|
edit/loop_end=-1
|
||||||
|
compress/mode=2
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="wav"
|
||||||
|
type="AudioStreamWAV"
|
||||||
|
uid="uid://dcpehnwdueyyo"
|
||||||
|
path="res://.godot/imported/Space Horror InGame Music (Exploration) _Clement Panchout.wav-b8a2c544037b0505487a02f0a4760b5c.sample"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/audio/music/Space Horror InGame Music (Exploration) _Clement Panchout.wav"
|
||||||
|
dest_files=["res://.godot/imported/Space Horror InGame Music (Exploration) _Clement Panchout.wav-b8a2c544037b0505487a02f0a4760b5c.sample"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
force/8_bit=false
|
||||||
|
force/mono=false
|
||||||
|
force/max_rate=false
|
||||||
|
force/max_rate_hz=44100
|
||||||
|
edit/trim=false
|
||||||
|
edit/normalize=false
|
||||||
|
edit/loop_mode=2
|
||||||
|
edit/loop_begin=0
|
||||||
|
edit/loop_end=-1
|
||||||
|
compress/mode=2
|
||||||
BIN
assets/audio/sfx/817587__silverdubloons__tick06.wav
Normal file
24
assets/audio/sfx/817587__silverdubloons__tick06.wav.import
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="wav"
|
||||||
|
type="AudioStreamWAV"
|
||||||
|
uid="uid://bafsd057v7yvg"
|
||||||
|
path="res://.godot/imported/817587__silverdubloons__tick06.wav-073a8f633d78aad3d77b2f7c8ae0c273.sample"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/audio/sfx/817587__silverdubloons__tick06.wav"
|
||||||
|
dest_files=["res://.godot/imported/817587__silverdubloons__tick06.wav-073a8f633d78aad3d77b2f7c8ae0c273.sample"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
force/8_bit=false
|
||||||
|
force/mono=false
|
||||||
|
force/max_rate=false
|
||||||
|
force/max_rate_hz=44100
|
||||||
|
edit/trim=false
|
||||||
|
edit/normalize=false
|
||||||
|
edit/loop_mode=0
|
||||||
|
edit/loop_begin=0
|
||||||
|
edit/loop_end=-1
|
||||||
|
compress/mode=2
|
||||||
52
assets/sources.yaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Asset Attribution and Source Information
|
||||||
|
# Every asset in this project must be documented here with complete metadata
|
||||||
|
# Required fields: source, license, attribution, modifications, usage
|
||||||
|
|
||||||
|
audio:
|
||||||
|
music:
|
||||||
|
"Space Horror InGame Music (Exploration) _Clement Panchout.wav":
|
||||||
|
source: "https://clement-panchout.itch.io/yet-another-free-music-pack"
|
||||||
|
license: "" # TODO: Verify license from source
|
||||||
|
attribution: "Space Horror InGame Music by Clement Panchout"
|
||||||
|
modifications: "Converted to WAV format, loop configuration applied in AudioManager"
|
||||||
|
usage: "Background music for all gameplay scenes and menus"
|
||||||
|
|
||||||
|
sfx:
|
||||||
|
"817587__silverdubloons__tick06.wav":
|
||||||
|
source: "https://freesound.org/people/SilverDubloons/sounds/817587/"
|
||||||
|
license: "" # TODO: Verify license from freesound.org
|
||||||
|
attribution: "Tick06 by SilverDubloons"
|
||||||
|
modifications: "None"
|
||||||
|
usage: "UI button click sound effects throughout the application"
|
||||||
|
|
||||||
|
sprites:
|
||||||
|
characters:
|
||||||
|
skeleton:
|
||||||
|
"assets/sprites/characters/skeleton/*":
|
||||||
|
source: "https://jesse-m.itch.io/skeleton-pack"
|
||||||
|
license: "" # TODO: Verify license from itch.io page
|
||||||
|
attribution: "Skeleton Pack by Jesse M"
|
||||||
|
modifications: ""
|
||||||
|
usage: "Placeholder for animation sprites"
|
||||||
|
|
||||||
|
skulls:
|
||||||
|
"assets/sprites/skulls/*":
|
||||||
|
source: "https://gitea.nett00n.org/nett00n/pixelart/src/branch/main/pixelorama/2025-skelly-assests"
|
||||||
|
license: "CC"
|
||||||
|
attribution: "Skelly icons by @nett00n"
|
||||||
|
modifications: ""
|
||||||
|
usage: ""
|
||||||
|
|
||||||
|
Referenced in original sources.yaml but file not found:
|
||||||
|
textures:
|
||||||
|
backgrounds:
|
||||||
|
"BG.pg":
|
||||||
|
source: "https://gitea.nett00n.org/nett00n/pixelart/src/branch/main/pixelorama/2025-skelly-assests"
|
||||||
|
license: "CC"
|
||||||
|
attribution: "Skelly icons by @nett00n"
|
||||||
|
modifications: ""
|
||||||
|
usage: ""
|
||||||
|
|
||||||
|
# TODO: Verify all license information by visiting source URLs
|
||||||
|
# TODO: Check for any missing assets not documented here
|
||||||
|
# TODO: Confirm all attribution text meets source requirements
|
||||||
BIN
assets/sprites/characters/skeleton/Skeleton Attack.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cowabod6jrn47"
|
||||||
|
path="res://.godot/imported/Skeleton Attack.png-31a9e5b2c10f873e155f012eeea3cf92.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/characters/skeleton/Skeleton Attack.png"
|
||||||
|
dest_files=["res://.godot/imported/Skeleton Attack.png-31a9e5b2c10f873e155f012eeea3cf92.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/characters/skeleton/Skeleton Dead.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
34
assets/sprites/characters/skeleton/Skeleton Dead.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://660wqxgwh8dr"
|
||||||
|
path="res://.godot/imported/Skeleton Dead.png-e1687d67e643bab4b8c92b164a03a547.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/characters/skeleton/Skeleton Dead.png"
|
||||||
|
dest_files=["res://.godot/imported/Skeleton Dead.png-e1687d67e643bab4b8c92b164a03a547.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/characters/skeleton/Skeleton Hit.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
34
assets/sprites/characters/skeleton/Skeleton Hit.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://btfjyc4jfhiii"
|
||||||
|
path="res://.godot/imported/Skeleton Hit.png-7311c991bcb76e5d6a77397d5558f61c.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/characters/skeleton/Skeleton Hit.png"
|
||||||
|
dest_files=["res://.godot/imported/Skeleton Hit.png-7311c991bcb76e5d6a77397d5558f61c.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/characters/skeleton/Skeleton Idle.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
34
assets/sprites/characters/skeleton/Skeleton Idle.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bcr4bokw87m5n"
|
||||||
|
path="res://.godot/imported/Skeleton Idle.png-6287b112f02c0e4ac23abbe600ade526.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/characters/skeleton/Skeleton Idle.png"
|
||||||
|
dest_files=["res://.godot/imported/Skeleton Idle.png-6287b112f02c0e4ac23abbe600ade526.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/characters/skeleton/Skeleton React.png
Normal file
|
After Width: | Height: | Size: 673 B |
34
assets/sprites/characters/skeleton/Skeleton React.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cuo2o785qopo3"
|
||||||
|
path="res://.godot/imported/Skeleton React.png-bae9c1ad14cb9e90c7d6e7d717e2699e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/characters/skeleton/Skeleton React.png"
|
||||||
|
dest_files=["res://.godot/imported/Skeleton React.png-bae9c1ad14cb9e90c7d6e7d717e2699e.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/characters/skeleton/Skeleton Walk.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
34
assets/sprites/characters/skeleton/Skeleton Walk.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b0dqy1at078ct"
|
||||||
|
path="res://.godot/imported/Skeleton Walk.png-19ec7f5950a5cbfa9505c0dc4dd1b48f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/characters/skeleton/Skeleton Walk.png"
|
||||||
|
dest_files=["res://.godot/imported/Skeleton Walk.png-19ec7f5950a5cbfa9505c0dc4dd1b48f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/blue.png
Normal file
|
After Width: | Height: | Size: 205 B |
34
assets/sprites/skulls/blue.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dxq2ab2uo3fel"
|
||||||
|
path="res://.godot/imported/blue.png-a5b81332a57b3efef9a75dd151a28d11.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/blue.png"
|
||||||
|
dest_files=["res://.godot/imported/blue.png-a5b81332a57b3efef9a75dd151a28d11.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/dark-blue.png
Normal file
|
After Width: | Height: | Size: 205 B |
34
assets/sprites/skulls/dark-blue.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bma78772dfdq3"
|
||||||
|
path="res://.godot/imported/dark-blue.png-59d632e889a5b721da0ae18edaed44bb.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/dark-blue.png"
|
||||||
|
dest_files=["res://.godot/imported/dark-blue.png-59d632e889a5b721da0ae18edaed44bb.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/green.png
Normal file
|
After Width: | Height: | Size: 197 B |
34
assets/sprites/skulls/green.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bodkdsn8aqcs0"
|
||||||
|
path="res://.godot/imported/green.png-ff6e1cc04288883fe02a8594d666e276.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/green.png"
|
||||||
|
dest_files=["res://.godot/imported/green.png-ff6e1cc04288883fe02a8594d666e276.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/grey.png
Normal file
|
After Width: | Height: | Size: 205 B |
34
assets/sprites/skulls/grey.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dnq7a0tfqs6xv"
|
||||||
|
path="res://.godot/imported/grey.png-ec35f235cffbff0246c0b496073088b5.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/grey.png"
|
||||||
|
dest_files=["res://.godot/imported/grey.png-ec35f235cffbff0246c0b496073088b5.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/orange.png
Normal file
|
After Width: | Height: | Size: 193 B |
34
assets/sprites/skulls/orange.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://iq603aympcro"
|
||||||
|
path="res://.godot/imported/orange.png-8b2bb01a523f1a7b73fd852bdfe7ef38.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/orange.png"
|
||||||
|
dest_files=["res://.godot/imported/orange.png-8b2bb01a523f1a7b73fd852bdfe7ef38.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/pink.png
Normal file
|
After Width: | Height: | Size: 195 B |
34
assets/sprites/skulls/pink.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ckslt30117ow5"
|
||||||
|
path="res://.godot/imported/pink.png-8e1ef4f0f945c54fb98f36f4cbd18350.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/pink.png"
|
||||||
|
dest_files=["res://.godot/imported/pink.png-8e1ef4f0f945c54fb98f36f4cbd18350.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/purple.png
Normal file
|
After Width: | Height: | Size: 198 B |
34
assets/sprites/skulls/purple.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dhxqh8wegngyu"
|
||||||
|
path="res://.godot/imported/purple.png-532ae50f2abc0def69a2a3e5012ac904.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/purple.png"
|
||||||
|
dest_files=["res://.godot/imported/purple.png-532ae50f2abc0def69a2a3e5012ac904.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/red.png
Normal file
|
After Width: | Height: | Size: 193 B |
34
assets/sprites/skulls/red.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://d4mn1p6620x5s"
|
||||||
|
path="res://.godot/imported/red.png-b1cc6fdfcc710fe28188480ae838b7ce.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/red.png"
|
||||||
|
dest_files=["res://.godot/imported/red.png-b1cc6fdfcc710fe28188480ae838b7ce.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/sprites/skulls/yellow.png
Normal file
|
After Width: | Height: | Size: 196 B |
34
assets/sprites/skulls/yellow.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://fw01lg2olk7f"
|
||||||
|
path="res://.godot/imported/yellow.png-b1e27f543291797e145a5e83f2d9c671.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sprites/skulls/yellow.png"
|
||||||
|
dest_files=["res://.godot/imported/yellow.png-b1e27f543291797e145a5e83f2d9c671.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/textures/backgrounds/BGx3.png
Normal file
|
After Width: | Height: | Size: 350 B |
34
assets/textures/backgrounds/BGx3.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bengv32u1jeym"
|
||||||
|
path="res://.godot/imported/BGx3.png-7878045c31a8f7297b620b7e42c1a5bf.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/textures/backgrounds/BGx3.png"
|
||||||
|
dest_files=["res://.godot/imported/BGx3.png-7878045c31a8f7297b620b7e42c1a5bf.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
15
data/default_bus_layout.tres
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[gd_resource type="AudioBusLayout" format=3 uid="uid://c4bwiq14nq074"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
bus/1/name = &"SFX"
|
||||||
|
bus/1/solo = false
|
||||||
|
bus/1/mute = false
|
||||||
|
bus/1/bypass_fx = false
|
||||||
|
bus/1/volume_db = 0.0
|
||||||
|
bus/1/send = &"Master"
|
||||||
|
bus/2/name = &"Music"
|
||||||
|
bus/2/solo = false
|
||||||
|
bus/2/mute = false
|
||||||
|
bus/2/bypass_fx = false
|
||||||
|
bus/2/volume_db = 0.0
|
||||||
|
bus/2/send = &"Master"
|
||||||
424
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
# System Architecture
|
||||||
|
|
||||||
|
High-level architecture guide for the Skelly project, explaining system design, architectural patterns, and design decisions.
|
||||||
|
|
||||||
|
**Quick Links**:
|
||||||
|
- **Coding Standards**: See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
||||||
|
- **Testing Protocols**: See [TESTING.md](TESTING.md)
|
||||||
|
- **Component APIs**: See [UI_COMPONENTS.md](UI_COMPONENTS.md)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Skelly uses a service-oriented architecture with **autoload managers** providing global services, **scene-based components** implementing gameplay, and **signal-based communication** for loose coupling.
|
||||||
|
|
||||||
|
**Key Architectural Principles**:
|
||||||
|
- **Instance-based design**: No global static state for testability
|
||||||
|
- **Signal-driven communication**: Loose coupling between systems
|
||||||
|
- **Service layer pattern**: Autoloads as singleton services
|
||||||
|
- **State machines**: Explicit state management for complex flows
|
||||||
|
- **Defensive programming**: Input validation, error recovery, fallback mechanisms
|
||||||
|
|
||||||
|
## Autoload System
|
||||||
|
|
||||||
|
Autoloads provide singleton services accessible globally. Each has a specific responsibility.
|
||||||
|
|
||||||
|
### GameManager
|
||||||
|
**Responsibility**: Scene transitions and game state coordination
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Centralized scene loading with error handling
|
||||||
|
- Race condition protection via `is_changing_scene` flag
|
||||||
|
- Scene path constants for maintainability
|
||||||
|
- Gameplay mode validation with whitelist
|
||||||
|
- Never use `get_tree().change_scene_to_file()` directly
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct
|
||||||
|
GameManager.start_game_with_mode("match3")
|
||||||
|
|
||||||
|
# ❌ Wrong
|
||||||
|
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Files**: `src/autoloads/GameManager.gd`
|
||||||
|
|
||||||
|
### SaveManager
|
||||||
|
**Responsibility**: Save/load game state with security and data integrity
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- **Tamper Detection**: Deterministic checksums detect save file modification
|
||||||
|
- **Race Condition Protection**: Save operation locking prevents concurrent conflicts
|
||||||
|
- **Permissive Validation**: Auto-repair system fixes corrupted data
|
||||||
|
- **Type Safety**: NaN/Infinity/bounds checking for numeric values
|
||||||
|
- **Memory Protection**: File size limits prevent memory exhaustion
|
||||||
|
- **Version Migration**: Backward-compatible save format upgrades
|
||||||
|
- **Error Recovery**: Multi-layered backup and fallback systems
|
||||||
|
|
||||||
|
**Security Model**:
|
||||||
|
```gdscript
|
||||||
|
# Checksum validates data integrity
|
||||||
|
save_data["_checksum"] = _calculate_checksum(save_data)
|
||||||
|
|
||||||
|
# Auto-repair fixes corrupted fields
|
||||||
|
if not _validate_save_data(data):
|
||||||
|
data = _repair_save_data(data)
|
||||||
|
|
||||||
|
# Race condition protection
|
||||||
|
if _is_saving:
|
||||||
|
return # Prevent concurrent saves
|
||||||
|
```
|
||||||
|
|
||||||
|
**Testing**: See [TESTING.md](TESTING.md#save-system-testing-protocols) for comprehensive test suites validating checksums, migration, and integration.
|
||||||
|
|
||||||
|
**Files**: `src/autoloads/SaveManager.gd`
|
||||||
|
|
||||||
|
### SettingsManager
|
||||||
|
**Responsibility**: User settings persistence and validation
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Input validation with NaN/Infinity checks
|
||||||
|
- Bounds checking for numeric values
|
||||||
|
- Security hardening against invalid inputs
|
||||||
|
- Default fallback values
|
||||||
|
- Type coercion with validation
|
||||||
|
|
||||||
|
**Files**: `src/autoloads/SettingsManager.gd`
|
||||||
|
|
||||||
|
### DebugManager
|
||||||
|
**Responsibility**: Unified logging and debug UI coordination
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Structured logging with log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
|
||||||
|
- Category-based log organization
|
||||||
|
- Global debug UI toggle (F12 key)
|
||||||
|
- Log level filtering for development/production
|
||||||
|
- Replaces all `print()` and `push_error()` calls
|
||||||
|
|
||||||
|
**Usage**: See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#logging-standards) for logging best practices.
|
||||||
|
|
||||||
|
**Files**: `src/autoloads/DebugManager.gd`
|
||||||
|
|
||||||
|
### AudioManager
|
||||||
|
**Responsibility**: Music and sound effect playback
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Audio bus system (Music, SFX)
|
||||||
|
- Volume control per bus
|
||||||
|
- Loop configuration for music
|
||||||
|
- UI click sounds
|
||||||
|
|
||||||
|
**Files**: `src/autoloads/AudioManager.gd`
|
||||||
|
|
||||||
|
### LocalizationManager
|
||||||
|
**Responsibility**: Language switching and translation management
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Multi-language support (English, Russian)
|
||||||
|
- Runtime language switching
|
||||||
|
- Translation file management
|
||||||
|
|
||||||
|
**Files**: `src/autoloads/LocalizationManager.gd`
|
||||||
|
|
||||||
|
## Scene Management Pattern
|
||||||
|
|
||||||
|
All scene transitions flow through **GameManager** to ensure consistent error handling and state management.
|
||||||
|
|
||||||
|
### Scene Loading Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
User Action
|
||||||
|
↓
|
||||||
|
GameManager.start_game_with_mode(mode)
|
||||||
|
↓
|
||||||
|
Validate mode (whitelist check)
|
||||||
|
↓
|
||||||
|
Check is_changing_scene flag
|
||||||
|
↓
|
||||||
|
Load PackedScene with validation
|
||||||
|
↓
|
||||||
|
change_scene_to_packed()
|
||||||
|
↓
|
||||||
|
Reset is_changing_scene flag
|
||||||
|
```
|
||||||
|
|
||||||
|
### Race Condition Prevention
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
var is_changing_scene: bool = false
|
||||||
|
|
||||||
|
func start_game_with_mode(gameplay_mode: String) -> void:
|
||||||
|
# Prevent concurrent scene changes
|
||||||
|
if is_changing_scene:
|
||||||
|
DebugManager.log_warn("Scene change already in progress", "GameManager")
|
||||||
|
return
|
||||||
|
|
||||||
|
is_changing_scene = true
|
||||||
|
# ... scene loading logic ...
|
||||||
|
is_changing_scene = false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why This Matters**: Multiple rapid button clicks or input events could trigger concurrent scene loads, causing crashes or undefined state.
|
||||||
|
|
||||||
|
## Modular Gameplay System
|
||||||
|
|
||||||
|
Game modes are implemented as separate gameplay modules in `scenes/game/gameplays/`.
|
||||||
|
|
||||||
|
### Gameplay Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Game.tscn (Main scene)
|
||||||
|
↓
|
||||||
|
├─> Match3Gameplay.tscn (Match-3 mode)
|
||||||
|
├─> ClickomaniaGameplay.tscn (Clickomania mode)
|
||||||
|
└─> [Future gameplay modes]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Each gameplay mode:
|
||||||
|
- Extends Control or Node2D
|
||||||
|
- Emits `score_changed` signal
|
||||||
|
- Implements `_ready()` for initialization
|
||||||
|
- Handles input independently
|
||||||
|
- Includes optional debug menu
|
||||||
|
|
||||||
|
**Files**: `scenes/game/gameplays/`
|
||||||
|
|
||||||
|
## Design Patterns Used
|
||||||
|
|
||||||
|
### Instance-Based Architecture
|
||||||
|
|
||||||
|
**Problem**: Static variables prevent testing and create hidden dependencies.
|
||||||
|
|
||||||
|
**Solution**: Instance-based architecture with explicit dependencies.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# ❌ Bad: Static global state
|
||||||
|
static var current_gem_pool = [0, 1, 2, 3, 4]
|
||||||
|
|
||||||
|
# ✅ Good: Instance-based
|
||||||
|
var active_gem_types: Array = []
|
||||||
|
|
||||||
|
func set_active_gem_types(gem_indices: Array) -> void:
|
||||||
|
active_gem_types = gem_indices.duplicate()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
- Each instance isolated for testing
|
||||||
|
- No hidden global state
|
||||||
|
- Explicit dependencies
|
||||||
|
- Thread-safe by default
|
||||||
|
|
||||||
|
### Signal-Based Communication
|
||||||
|
|
||||||
|
**Pattern**: Use signals for loose coupling between systems.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# Component emits signal
|
||||||
|
signal score_changed(new_score: int)
|
||||||
|
|
||||||
|
# Parent connects to signal
|
||||||
|
gameplay.score_changed.connect(_on_score_changed)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
- Loose coupling
|
||||||
|
- Easy to test components in isolation
|
||||||
|
- Clear event flow
|
||||||
|
- Flexible subscription model
|
||||||
|
|
||||||
|
### Service Layer Pattern
|
||||||
|
|
||||||
|
Autoloads act as singleton services providing global functionality.
|
||||||
|
|
||||||
|
**Pattern**:
|
||||||
|
- Autoloads expose public API methods
|
||||||
|
- Components call autoload methods
|
||||||
|
- Autoloads emit signals for state changes
|
||||||
|
- Never nest autoload calls deeply
|
||||||
|
|
||||||
|
### State Machine Pattern
|
||||||
|
|
||||||
|
Complex workflows use explicit state machines.
|
||||||
|
|
||||||
|
**Example**: Match-3 tile swapping
|
||||||
|
```
|
||||||
|
WAITING → SELECTING → SWAPPING → PROCESSING → WAITING
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
- Clear state transitions
|
||||||
|
- Easy to debug
|
||||||
|
- Prevents invalid state combinations
|
||||||
|
- Self-documenting code
|
||||||
|
|
||||||
|
## Critical Architecture Decisions
|
||||||
|
|
||||||
|
### Memory Management: queue_free() Over free()
|
||||||
|
|
||||||
|
**Decision**: Always use `queue_free()` instead of `free()` for node cleanup.
|
||||||
|
|
||||||
|
**Rationale**:
|
||||||
|
- `free()` causes immediate deletion (crashes if referenced)
|
||||||
|
- `queue_free()` waits until safe deletion point
|
||||||
|
- Prevents use-after-free bugs
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct
|
||||||
|
for child in children_to_remove:
|
||||||
|
child.queue_free()
|
||||||
|
await get_tree().process_frame # Wait for cleanup
|
||||||
|
|
||||||
|
# ❌ Dangerous
|
||||||
|
for child in children_to_remove:
|
||||||
|
child.free() # Can crash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Impact**: Eliminated multiple potential crash points. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#memory-management-standards) for standards.
|
||||||
|
|
||||||
|
### Race Condition Prevention: State Flags
|
||||||
|
|
||||||
|
**Decision**: Use state flags to prevent concurrent operations.
|
||||||
|
|
||||||
|
**Rationale**:
|
||||||
|
- Async operations can overlap without protection
|
||||||
|
- Multiple rapid inputs can trigger race conditions
|
||||||
|
- State flags provide simple, effective protection
|
||||||
|
|
||||||
|
**Implementation**: Used in GameManager (scene loading), SaveManager (save operations), and gameplay systems (tile swapping).
|
||||||
|
|
||||||
|
### Error Recovery: Fallback Mechanisms
|
||||||
|
|
||||||
|
**Decision**: Provide fallback behavior for all critical failures.
|
||||||
|
|
||||||
|
**Rationale**:
|
||||||
|
- Games should degrade gracefully, not crash
|
||||||
|
- User experience > strict validation
|
||||||
|
- Log errors but continue operation
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
- Settings file corrupted? Load defaults
|
||||||
|
- Scene load failed? Return to main menu
|
||||||
|
- Audio file missing? Continue without sound
|
||||||
|
|
||||||
|
### Input Validation: Whitelist Approach
|
||||||
|
|
||||||
|
**Decision**: Validate all user inputs against known-good values.
|
||||||
|
|
||||||
|
**Rationale**:
|
||||||
|
- Security hardening
|
||||||
|
- Prevent invalid state
|
||||||
|
- Clear error messages
|
||||||
|
- Self-documenting code
|
||||||
|
|
||||||
|
**Implementation**: Used in SettingsManager (volume, language), GameManager (gameplay modes), Match3Gameplay (grid movements).
|
||||||
|
|
||||||
|
## System Interactions
|
||||||
|
|
||||||
|
### Typical Game Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Main Menu
|
||||||
|
↓
|
||||||
|
[GameManager.start_game_with_mode("match3")]
|
||||||
|
↓
|
||||||
|
Game Scene Loads
|
||||||
|
↓
|
||||||
|
Match3Gameplay initializes
|
||||||
|
↓
|
||||||
|
├─> SettingsManager (load difficulty)
|
||||||
|
├─> AudioManager (play background music)
|
||||||
|
└─> DebugManager (setup debug UI)
|
||||||
|
↓
|
||||||
|
Gameplay Loop
|
||||||
|
↓
|
||||||
|
├─> Input handling
|
||||||
|
├─> Score updates (emit score_changed signal)
|
||||||
|
├─> SaveManager (autosave high score)
|
||||||
|
└─> DebugManager (log important events)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Signal Flow Example: Score Change
|
||||||
|
|
||||||
|
```
|
||||||
|
Match3Gameplay detects match
|
||||||
|
↓
|
||||||
|
[emit score_changed(new_score)]
|
||||||
|
↓
|
||||||
|
Game.gd receives signal
|
||||||
|
↓
|
||||||
|
Updates score display UI
|
||||||
|
↓
|
||||||
|
SaveManager.save_high_score(score)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug System Integration
|
||||||
|
|
||||||
|
```
|
||||||
|
User presses F12
|
||||||
|
↓
|
||||||
|
DebugManager.toggle_debug_ui()
|
||||||
|
↓
|
||||||
|
[emit debug_ui_toggled(visible)]
|
||||||
|
↓
|
||||||
|
All debug menus receive signal
|
||||||
|
↓
|
||||||
|
Show/hide debug panels
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quality Improvements
|
||||||
|
|
||||||
|
The project implements high-quality code standards from the start:
|
||||||
|
|
||||||
|
**Key Quality Features**:
|
||||||
|
- **Memory Safety**: Uses `queue_free()` pattern for safe node cleanup
|
||||||
|
- **Error Handling**: Comprehensive error handling with fallbacks
|
||||||
|
- **Race Condition Protection**: State flag protection for async operations
|
||||||
|
- **Instance-Based Architecture**: No global static state for testability
|
||||||
|
- **Code Reuse**: Base class architecture (DebugMenuBase) for common functionality
|
||||||
|
- **Input Validation**: Complete validation coverage for all user inputs
|
||||||
|
|
||||||
|
These standards are enforced through the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist) quality checklist.
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Extending the Architecture
|
||||||
|
|
||||||
|
When adding new features:
|
||||||
|
|
||||||
|
1. **New autoload?**
|
||||||
|
- Only if truly global and used by many systems
|
||||||
|
- Consider dependency injection instead
|
||||||
|
- Keep autoloads focused on single responsibility
|
||||||
|
|
||||||
|
2. **New gameplay mode?**
|
||||||
|
- Create in `scenes/game/gameplays/`
|
||||||
|
- Extend appropriate base class
|
||||||
|
- Emit `score_changed` signal
|
||||||
|
- Add to GameManager mode whitelist
|
||||||
|
|
||||||
|
3. **New UI component?**
|
||||||
|
- Create in `scenes/ui/components/`
|
||||||
|
- Follow [UI_COMPONENTS.md](UI_COMPONENTS.md) patterns
|
||||||
|
- Support keyboard/gamepad navigation
|
||||||
|
- Emit signals for state changes
|
||||||
|
|
||||||
|
### Architecture Review Checklist
|
||||||
|
|
||||||
|
Before committing architectural changes:
|
||||||
|
|
||||||
|
- [ ] No new global static state introduced
|
||||||
|
- [ ] All autoload access justified and documented
|
||||||
|
- [ ] Signal-based communication used appropriately
|
||||||
|
- [ ] Error handling with fallbacks implemented
|
||||||
|
- [ ] Input validation in place
|
||||||
|
- [ ] Memory management uses `queue_free()`
|
||||||
|
- [ ] Race condition protection if async operations
|
||||||
|
- [ ] Testing strategy defined
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)**: Coding standards and best practices
|
||||||
|
- **[TESTING.md](TESTING.md)**: Testing protocols and procedures
|
||||||
|
- **[UI_COMPONENTS.md](UI_COMPONENTS.md)**: Component API reference
|
||||||
|
- **[CLAUDE.md](CLAUDE.md)**: LLM assistant quick start guide
|
||||||
655
docs/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,655 @@
|
|||||||
|
# Code of Conduct - Skelly Project
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Coding standards and development practices for the Skelly project. These guidelines help developers contribute effectively while maintaining code quality and project consistency.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
### 1. Code Clarity Over Cleverness
|
||||||
|
- Write code that is easy to read
|
||||||
|
- Use descriptive variable and function names
|
||||||
|
- Prefer explicit code over "clever" solutions
|
||||||
|
- Comment complex logic and business rules
|
||||||
|
|
||||||
|
### 2. Consistency First
|
||||||
|
- Follow existing code patterns
|
||||||
|
- Use same naming conventions throughout
|
||||||
|
- Maintain consistent indentation and formatting
|
||||||
|
- Follow Godot's GDScript style guide
|
||||||
|
|
||||||
|
### 3. Incremental Development
|
||||||
|
- Make small, focused commits
|
||||||
|
- Test changes before committing
|
||||||
|
- Don't break existing functionality
|
||||||
|
- Use debug system to verify changes
|
||||||
|
|
||||||
|
## GDScript Coding Standards
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
> 📋 **Quick Reference**: For complete naming convention details, see the **[Naming Convention Quick Reference](#naming-convention-quick-reference)** section below.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# Variables and functions: snake_case
|
||||||
|
var player_health: int = 100
|
||||||
|
func calculate_damage() -> int:
|
||||||
|
|
||||||
|
# Constants: SCREAMING_SNAKE_CASE
|
||||||
|
const MAX_HEALTH := 100
|
||||||
|
const TILE_SPACING := 54
|
||||||
|
|
||||||
|
# Classes: PascalCase
|
||||||
|
class_name PlayerController
|
||||||
|
|
||||||
|
# Scene files (.tscn) and Script files (.gd): PascalCase
|
||||||
|
# MainMenu.tscn, MainMenu.gd
|
||||||
|
# Match3Gameplay.tscn, Match3Gameplay.gd
|
||||||
|
# TestAudioManager.gd (test files)
|
||||||
|
|
||||||
|
# Signals: past_tense
|
||||||
|
signal health_changed
|
||||||
|
signal game_started
|
||||||
|
|
||||||
|
# Private functions: prefix with underscore
|
||||||
|
func _ready():
|
||||||
|
func _initialize_grid():
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
```gdscript
|
||||||
|
# 1. extends statement
|
||||||
|
extends Node2D
|
||||||
|
|
||||||
|
# 2. class_name (if applicable)
|
||||||
|
class_name Match3Controller
|
||||||
|
|
||||||
|
# 3. Constants
|
||||||
|
const GRID_SIZE := Vector2i(8, 8)
|
||||||
|
|
||||||
|
# 4. Signals
|
||||||
|
signal match_found(tiles: Array)
|
||||||
|
|
||||||
|
# 5. Variables
|
||||||
|
var grid := []
|
||||||
|
@onready var debug_ui = $DebugUI
|
||||||
|
|
||||||
|
# 6. Functions (lifecycle first, then custom)
|
||||||
|
func _ready():
|
||||||
|
func _process(delta):
|
||||||
|
func custom_function():
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation Requirements
|
||||||
|
```gdscript
|
||||||
|
# Required for all public functions
|
||||||
|
func set_gem_pool(gem_indices: Array) -> void:
|
||||||
|
"""Set specific gem types as the active pool"""
|
||||||
|
_update_all_tiles_gem_pool(gem_indices)
|
||||||
|
print("Set gem pool to: ", gem_indices)
|
||||||
|
|
||||||
|
# Document complex algorithms
|
||||||
|
func _get_match_line(start: Vector2i, dir: Vector2i) -> Array:
|
||||||
|
"""Find all matching tiles in a line from start position in given direction"""
|
||||||
|
var line = [grid[start.y][start.x]]
|
||||||
|
var type = grid[start.y][start.x].tile_type
|
||||||
|
# Implementation...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project-Specific Guidelines
|
||||||
|
|
||||||
|
### Scene Management
|
||||||
|
- All scene transitions go through `GameManager`
|
||||||
|
- Never use `get_tree().change_scene_to_file()` directly
|
||||||
|
- Define scene paths as constants in GameManager
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct
|
||||||
|
GameManager.start_match3_game()
|
||||||
|
|
||||||
|
# ❌ Wrong
|
||||||
|
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Autoload Usage
|
||||||
|
- Use autoloads for global state only
|
||||||
|
- Don't access autoloads from deeply nested components
|
||||||
|
- Pass data through signals or direct references when possible
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct - using signals
|
||||||
|
signal settings_changed
|
||||||
|
SettingsManager.volume_changed.connect(_on_volume_changed)
|
||||||
|
|
||||||
|
# ✅ Also correct - direct access for global state
|
||||||
|
var current_language = SettingsManager.get_setting("language")
|
||||||
|
|
||||||
|
# ❌ Wrong - tight coupling
|
||||||
|
func some_deep_function():
|
||||||
|
SettingsManager.set_setting("volume", 0.5) # Too deep in call stack
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug System Integration
|
||||||
|
- Always connect to DebugManager signals for debug UI
|
||||||
|
- Use structured logging instead of plain print statements
|
||||||
|
- Remove temporary debug logs before committing (unless permanently useful)
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct debug integration
|
||||||
|
func _connect_to_global_debug() -> void:
|
||||||
|
DebugManager.debug_ui_toggled.connect(_on_debug_ui_toggled)
|
||||||
|
debug_ui.visible = DebugManager.is_debug_ui_visible()
|
||||||
|
|
||||||
|
# ✅ Good structured logging
|
||||||
|
DebugManager.log_debug("Debug UI toggled to: " + str(visible), "Match3")
|
||||||
|
DebugManager.log_info("Initialized " + str(tiles.size()) + " tiles", "TileSystem")
|
||||||
|
|
||||||
|
# ❌ Bad logging practices
|
||||||
|
print("test") # Not descriptive, use structured logging
|
||||||
|
print(some_variable) # No context, use proper log level
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logging Standards
|
||||||
|
- Use `DebugManager.log_*()` functions instead of `print()` or `push_error()`
|
||||||
|
- Choose log levels based on message importance and audience
|
||||||
|
- Include categories to organize log output by system/component
|
||||||
|
- Format messages with clear, descriptive text and relevant context
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct logging usage
|
||||||
|
DebugManager.log_info("Game scene loaded successfully", "SceneManager")
|
||||||
|
DebugManager.log_warn("Audio file not found, using default", "AudioManager")
|
||||||
|
DebugManager.log_error("Failed to save settings: " + error_message, "Settings")
|
||||||
|
|
||||||
|
# ❌ Wrong approaches
|
||||||
|
print("loaded") # Use DebugManager.log_info() with category
|
||||||
|
push_error("error") # Use DebugManager.log_error() with context
|
||||||
|
if debug_mode: print("debug info") # Use DebugManager.log_debug()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asset Management
|
||||||
|
- **Document every asset** in `assets/sources.yaml`
|
||||||
|
- Include source information, license details, and attribution
|
||||||
|
- Document modifications made to original assets
|
||||||
|
- Verify license compatibility before adding assets
|
||||||
|
- Update sources.yaml in same commit as adding asset
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct asset addition workflow
|
||||||
|
# 1. Add asset file to appropriate assets/ subdirectory
|
||||||
|
# 2. Update assets/sources.yaml with complete metadata
|
||||||
|
# 3. Commit both files together with descriptive message
|
||||||
|
|
||||||
|
# Example sources.yaml entry:
|
||||||
|
# audio:
|
||||||
|
# sfx:
|
||||||
|
# "button_click.wav":
|
||||||
|
# source: "https://freesound.org/people/user/sounds/12345/"
|
||||||
|
# license: "CC BY 3.0"
|
||||||
|
# attribution: "Button Click by SoundArtist"
|
||||||
|
# modifications: "Normalized volume, trimmed silence"
|
||||||
|
# usage: "UI button interactions"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Check if resources loaded successfully
|
||||||
|
- Use `DebugManager.log_error()` for critical failures
|
||||||
|
- Provide fallback behavior when possible
|
||||||
|
- Include meaningful context in error messages
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# Good error handling with structured logging
|
||||||
|
func load_scene(path: String) -> void:
|
||||||
|
var packed_scene := load(path)
|
||||||
|
if not packed_scene or not packed_scene is PackedScene:
|
||||||
|
DebugManager.log_error("Failed to load scene at: %s" % path, "SceneLoader")
|
||||||
|
return
|
||||||
|
DebugManager.log_info("Successfully loaded scene: %s" % path, "SceneLoader")
|
||||||
|
get_tree().change_scene_to_packed(packed_scene)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Management Standards
|
||||||
|
|
||||||
|
**Critical Rules**:
|
||||||
|
1. **Always use `queue_free()`** instead of `free()` for node cleanup
|
||||||
|
2. **Wait for frame completion** after queueing nodes for removal
|
||||||
|
3. **Clear references before cleanup** to prevent access to freed memory
|
||||||
|
4. **Connect signals properly** for dynamically created nodes
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct memory management
|
||||||
|
for child in children_to_remove:
|
||||||
|
child.queue_free()
|
||||||
|
await get_tree().process_frame # Wait for cleanup
|
||||||
|
|
||||||
|
# ❌ Dangerous pattern (causes crashes)
|
||||||
|
for child in children_to_remove:
|
||||||
|
child.free() # Immediate deletion can crash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why This Matters**: Using `free()` causes immediate deletion which can crash if the node is still referenced elsewhere. `queue_free()` waits until it's safe to delete.
|
||||||
|
|
||||||
|
**See Also**: [ARCHITECTURE.md](ARCHITECTURE.md#memory-management-queue_free-over-free) for architectural rationale.
|
||||||
|
|
||||||
|
### Input Validation Standards
|
||||||
|
|
||||||
|
All user inputs must be validated before processing.
|
||||||
|
|
||||||
|
**Validation Requirements**:
|
||||||
|
1. **Type checking**: Verify input types before processing
|
||||||
|
2. **Bounds checking**: Validate numeric ranges and array indices
|
||||||
|
3. **Null checking**: Handle null and empty inputs gracefully
|
||||||
|
4. **Whitelist validation**: Validate against known good values
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# ✅ Proper input validation
|
||||||
|
func set_volume(value: float, setting_key: String) -> bool:
|
||||||
|
# Whitelist validation
|
||||||
|
if not setting_key in ["master_volume", "music_volume", "sfx_volume"]:
|
||||||
|
DebugManager.log_error("Invalid volume setting key: " + str(setting_key), "Settings")
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Bounds checking
|
||||||
|
var clamped_value = clamp(value, 0.0, 1.0)
|
||||||
|
if clamped_value != value:
|
||||||
|
DebugManager.log_warn("Volume value clamped from %f to %f" % [value, clamped_value], "Settings")
|
||||||
|
|
||||||
|
SettingsManager.set_setting(setting_key, clamped_value)
|
||||||
|
return true
|
||||||
|
|
||||||
|
# ✅ Grid movement validation
|
||||||
|
func _move_cursor(direction: Vector2i) -> void:
|
||||||
|
# Bounds checking
|
||||||
|
if abs(direction.x) > 1 or abs(direction.y) > 1:
|
||||||
|
DebugManager.log_error("Invalid cursor direction: " + str(direction), "Match3")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Null/empty checking
|
||||||
|
if not grid or grid.is_empty():
|
||||||
|
DebugManager.log_error("Grid not initialized", "Match3")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process validated input
|
||||||
|
var new_pos = cursor_pos + direction
|
||||||
|
# ... continue with validated data
|
||||||
|
```
|
||||||
|
|
||||||
|
**See Also**: [ARCHITECTURE.md](ARCHITECTURE.md#input-validation-whitelist-approach) for architectural decision rationale.
|
||||||
|
|
||||||
|
## Code Quality Checklist
|
||||||
|
|
||||||
|
Before committing code, verify:
|
||||||
|
|
||||||
|
- [ ] All user inputs validated (type, bounds, null checks)
|
||||||
|
- [ ] Error handling with fallback mechanisms implemented
|
||||||
|
- [ ] Memory cleanup uses `queue_free()` not `free()`
|
||||||
|
- [ ] No global static state introduced
|
||||||
|
- [ ] Proper logging with categories and appropriate levels
|
||||||
|
- [ ] Race condition protection for async operations
|
||||||
|
- [ ] Input actions defined in project input map
|
||||||
|
- [ ] Resources loaded with null/type checking
|
||||||
|
- [ ] Documentation updated for new public APIs
|
||||||
|
- [ ] Test coverage for new functionality
|
||||||
|
|
||||||
|
## Git Workflow
|
||||||
|
|
||||||
|
### Commit Guidelines
|
||||||
|
- Use clear, descriptive commit messages
|
||||||
|
- Start with a verb in imperative mood
|
||||||
|
- Keep first line under 50 characters
|
||||||
|
- Add body if needed for complex changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Good commit messages
|
||||||
|
Add gem pool management to match-3 system
|
||||||
|
Fix debug UI visibility toggle issue
|
||||||
|
Update documentation for new debug system
|
||||||
|
|
||||||
|
# Bad commit messages
|
||||||
|
fix bug
|
||||||
|
update
|
||||||
|
wip
|
||||||
|
```
|
||||||
|
|
||||||
|
### Branch Management
|
||||||
|
- Create feature branches for new functionality
|
||||||
|
- Use descriptive branch names: `feature/debug-ui`, `fix/tile-positioning`
|
||||||
|
- Keep branches focused on single features
|
||||||
|
- Delete branches after merging
|
||||||
|
|
||||||
|
### Before Committing
|
||||||
|
1. Test your changes in the Godot editor
|
||||||
|
2. Check that existing functionality still works
|
||||||
|
3. Use the debug system to verify your implementation
|
||||||
|
4. Run the project and navigate through affected areas
|
||||||
|
5. Remove temporary debug code
|
||||||
|
|
||||||
|
## Code Review Guidelines
|
||||||
|
|
||||||
|
### For Reviewers
|
||||||
|
- Focus on code clarity and maintainability
|
||||||
|
- Check for adherence to project patterns
|
||||||
|
- Verify that autoloads are used appropriately
|
||||||
|
- Ensure debug integration is properly implemented
|
||||||
|
- Test the changes yourself
|
||||||
|
|
||||||
|
### For Contributors
|
||||||
|
- Explain complex logic in commit messages or comments
|
||||||
|
- Provide context for why changes were made
|
||||||
|
- Test edge cases and error conditions
|
||||||
|
- Ask questions if project patterns are unclear
|
||||||
|
|
||||||
|
## Testing Approach
|
||||||
|
|
||||||
|
### Manual Testing Requirements
|
||||||
|
- Test in Godot editor with F5 run
|
||||||
|
- Verify debug UI works with F12 toggle
|
||||||
|
- Check scene transitions work
|
||||||
|
- Test on different screen sizes (mobile target)
|
||||||
|
- Verify audio and settings integration
|
||||||
|
|
||||||
|
### Debug System Testing
|
||||||
|
- Ensure debug panels appear/disappear correctly
|
||||||
|
- Test all debug buttons and controls
|
||||||
|
- Verify debug state persists across scene changes
|
||||||
|
- Check debug code doesn't affect release builds
|
||||||
|
|
||||||
|
## Naming Convention Quick Reference
|
||||||
|
|
||||||
|
> 🎯 **Single Source of Truth**: This section contains all naming conventions for the Skelly project. All other documentation files reference this section to avoid duplication and ensure consistency.
|
||||||
|
|
||||||
|
### 1. GDScript Code Elements
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# Variables and functions: snake_case
|
||||||
|
var player_health: int = 100
|
||||||
|
func calculate_damage() -> int:
|
||||||
|
|
||||||
|
# Constants: SCREAMING_SNAKE_CASE
|
||||||
|
const MAX_HEALTH := 100
|
||||||
|
const TILE_SPACING := 54
|
||||||
|
|
||||||
|
# Classes: PascalCase
|
||||||
|
class_name PlayerController
|
||||||
|
|
||||||
|
# Signals: past_tense_with_underscores
|
||||||
|
signal health_changed
|
||||||
|
signal game_started
|
||||||
|
signal match_found
|
||||||
|
|
||||||
|
# Private functions: prefix with underscore
|
||||||
|
func _ready():
|
||||||
|
func _initialize_grid():
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. File Naming Standards
|
||||||
|
|
||||||
|
#### Script and Scene Files
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct: All .gd and .tscn files use PascalCase
|
||||||
|
MainMenu.tscn / MainMenu.gd
|
||||||
|
Match3Gameplay.tscn / Match3Gameplay.gd
|
||||||
|
ClickomaniaGameplay.tscn / ClickomaniaGameplay.gd
|
||||||
|
ValueStepper.tscn / ValueStepper.gd
|
||||||
|
|
||||||
|
# Test files: PascalCase with "Test" prefix
|
||||||
|
TestAudioManager.gd
|
||||||
|
TestGameManager.gd
|
||||||
|
TestMatch3Gameplay.gd
|
||||||
|
|
||||||
|
# ❌ Wrong: Old snake_case style (being migrated)
|
||||||
|
main_menu.tscn / main_menu.gd
|
||||||
|
TestAudioManager.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- Scene files (.tscn) must match their script file name exactly
|
||||||
|
- All new files must use PascalCase
|
||||||
|
- Test files use "Test" prefix + PascalCase
|
||||||
|
- Autoload scripts follow PascalCase (GameManager.gd, AudioManager.gd)
|
||||||
|
|
||||||
|
### 3. Directory Naming Conventions
|
||||||
|
|
||||||
|
#### Source Code Directories
|
||||||
|
```
|
||||||
|
# Source directories: snake_case
|
||||||
|
src/autoloads/
|
||||||
|
scenes/game/gameplays/
|
||||||
|
scenes/ui/components/
|
||||||
|
tests/helpers/
|
||||||
|
|
||||||
|
# Root directories: lowercase
|
||||||
|
docs/
|
||||||
|
tests/
|
||||||
|
tools/
|
||||||
|
data/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Asset Directories
|
||||||
|
```
|
||||||
|
# Asset directories: kebab-case
|
||||||
|
assets/audio-files/
|
||||||
|
assets/ui-sprites/
|
||||||
|
assets/game-textures/
|
||||||
|
assets/fonts/
|
||||||
|
localization/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Resource and Configuration Files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Configuration files: lowercase with dots
|
||||||
|
project.godot
|
||||||
|
gdlintrc
|
||||||
|
.gdformatrc
|
||||||
|
.editorconfig
|
||||||
|
export_presets.cfg
|
||||||
|
|
||||||
|
# Godot resource files (.tres): PascalCase
|
||||||
|
data/DefaultBusLayout.tres
|
||||||
|
data/PlayerSaveData.tres
|
||||||
|
scenes/ui/DefaultTheme.tres
|
||||||
|
|
||||||
|
# Asset metadata: kebab-case
|
||||||
|
assets/asset-sources.yaml
|
||||||
|
assets/audio-files/audio-sources.yaml
|
||||||
|
assets/ui-sprites/sprite-sources.yaml
|
||||||
|
|
||||||
|
# Development files: kebab-case
|
||||||
|
requirements.txt
|
||||||
|
development-tools.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Asset File Naming
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Audio files: kebab-case in kebab-case directories
|
||||||
|
assets/audio-files/background-music.ogg
|
||||||
|
assets/audio-files/ui-sounds/button-click.wav
|
||||||
|
assets/audio-files/game-sounds/match-sound.wav
|
||||||
|
|
||||||
|
# Visual assets: kebab-case
|
||||||
|
assets/ui-sprites/main-menu-background.png
|
||||||
|
assets/game-textures/gem-blue.png
|
||||||
|
assets/fonts/main-ui-font.ttf
|
||||||
|
|
||||||
|
# Import settings: match the original file
|
||||||
|
background-music.ogg.import
|
||||||
|
button-click.wav.import
|
||||||
|
```
|
||||||
|
|
||||||
|
**Asset Rules:**
|
||||||
|
- All asset files use kebab-case
|
||||||
|
- Organized in kebab-case directories
|
||||||
|
- Import files automatically match asset names
|
||||||
|
- Document all assets in `asset-sources.yaml`
|
||||||
|
|
||||||
|
### 6. Git Workflow Conventions
|
||||||
|
|
||||||
|
#### Branch Naming
|
||||||
|
```bash
|
||||||
|
# Feature branches: feature/description-with-hyphens
|
||||||
|
feature/new-gameplay-mode
|
||||||
|
feature/settings-ui-improvement
|
||||||
|
feature/audio-system-upgrade
|
||||||
|
|
||||||
|
# Bug fixes: fix/description-with-hyphens
|
||||||
|
fix/tile-positioning-bug
|
||||||
|
fix/save-data-corruption
|
||||||
|
fix/debug-menu-visibility
|
||||||
|
|
||||||
|
# Refactoring: refactor/component-name
|
||||||
|
refactor/match3-input-system
|
||||||
|
refactor/autoload-structure
|
||||||
|
|
||||||
|
# Documentation: docs/section-name
|
||||||
|
docs/code-of-conduct-update
|
||||||
|
docs/api-documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Commit Message Format
|
||||||
|
```bash
|
||||||
|
# Format: <type>: <description>
|
||||||
|
# Examples:
|
||||||
|
feat: add dark mode toggle to settings menu
|
||||||
|
fix: resolve tile swap animation timing issue
|
||||||
|
docs: update naming conventions in code of conduct
|
||||||
|
refactor: migrate print statements to DebugManager
|
||||||
|
test: add comprehensive match3 validation tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Quick Reference Summary
|
||||||
|
|
||||||
|
| File Type | Convention | Example |
|
||||||
|
|-----------|------------|---------|
|
||||||
|
| **GDScript Files** | PascalCase | `MainMenu.gd`, `AudioManager.gd` |
|
||||||
|
| **Scene Files** | PascalCase | `MainMenu.tscn`, `Match3Gameplay.tscn` |
|
||||||
|
| **Test Files** | Test + PascalCase | `TestAudioManager.gd` |
|
||||||
|
| **Variables/Functions** | snake_case | `player_health`, `calculate_damage()` |
|
||||||
|
| **Constants** | SCREAMING_SNAKE_CASE | `MAX_HEALTH`, `TILE_SPACING` |
|
||||||
|
| **Classes** | PascalCase | `class_name PlayerController` |
|
||||||
|
| **Signals** | past_tense | `health_changed`, `game_started` |
|
||||||
|
| **Directories** | snake_case (src) / kebab-case (assets) | `src/autoloads/`, `assets/audio-files/` |
|
||||||
|
| **Assets** | kebab-case | `background-music.ogg`, `gem-blue.png` |
|
||||||
|
| **Config Files** | lowercase.extension | `project.godot`, `.gdformatrc` |
|
||||||
|
| **Branches** | type/kebab-case | `feature/new-gameplay`, `fix/tile-bug` |
|
||||||
|
|
||||||
|
> ✅ **Status**: All major file naming inconsistencies have been resolved. The project now follows consistent PascalCase naming for all .gd and .tscn files.
|
||||||
|
|
||||||
|
### 8. File Renaming Migration Guide
|
||||||
|
|
||||||
|
When renaming files to follow conventions:
|
||||||
|
|
||||||
|
**Step-by-step procedure:**
|
||||||
|
1. **Use Git rename**: `git mv old_file.gd NewFile.gd` (preserves history)
|
||||||
|
2. **Update .tscn references**: Modify script path in scene files
|
||||||
|
3. **Update code references**: Search and replace all `preload()` and `load()` statements
|
||||||
|
4. **Update project.godot**: If file is referenced in autoloads or project settings
|
||||||
|
5. **Update documentation**: Search all .md files for old references
|
||||||
|
6. **Update test files**: Modify any test files that reference the renamed file
|
||||||
|
7. **Run validation**: Execute `gdlint`, `gdformat`, and project tests
|
||||||
|
8. **Verify in editor**: Load scenes in Godot editor to confirm everything works
|
||||||
|
|
||||||
|
**Tools for validation:**
|
||||||
|
- `python tools/run_development.py --test` - Run all tests
|
||||||
|
- `python tools/run_development.py --lint` - Check code quality
|
||||||
|
- `python tools/run_development.py --format` - Ensure consistent formatting
|
||||||
|
|
||||||
|
## Common Mistakes to Avoid
|
||||||
|
|
||||||
|
### Architecture Violations
|
||||||
|
```gdscript
|
||||||
|
# Don't bypass GameManager
|
||||||
|
get_tree().change_scene_to_file("some_scene.tscn")
|
||||||
|
|
||||||
|
# Don't hardcode paths
|
||||||
|
var tile = load("res://scenes/game/gameplays/Tile.tscn")
|
||||||
|
|
||||||
|
# Don't ignore null checks
|
||||||
|
var node = get_node("SomeNode")
|
||||||
|
node.do_something() # Could crash if node doesn't exist
|
||||||
|
|
||||||
|
# Don't create global state in random scripts
|
||||||
|
# Use autoloads instead
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asset Management Violations
|
||||||
|
```gdscript
|
||||||
|
# Don't add assets without documentation
|
||||||
|
# Adding audio/new_music.mp3 without updating sources.yaml
|
||||||
|
|
||||||
|
# Don't use assets without verifying licenses
|
||||||
|
# Using copyrighted music without permission
|
||||||
|
|
||||||
|
# Don't modify assets without documenting changes
|
||||||
|
# Editing sprites without noting modifications in sources.yaml
|
||||||
|
|
||||||
|
# Don't commit assets and documentation separately
|
||||||
|
git add assets/sprites/new_sprite.png
|
||||||
|
git commit -m "add sprite" # Missing sources.yaml update
|
||||||
|
|
||||||
|
# Correct approach
|
||||||
|
git add assets/sprites/new_sprite.png assets/sources.yaml
|
||||||
|
git commit -m "add new sprite with attribution"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Issues
|
||||||
|
```gdscript
|
||||||
|
# Don't search nodes repeatedly
|
||||||
|
func _process(delta):
|
||||||
|
var ui = get_node("UI") # Expensive every frame
|
||||||
|
|
||||||
|
# Cache node references
|
||||||
|
@onready var ui = $UI
|
||||||
|
func _process(delta):
|
||||||
|
ui.update_display() # Much better
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug System Misuse
|
||||||
|
```gdscript
|
||||||
|
# Don't create separate debug systems
|
||||||
|
var my_debug_enabled = false
|
||||||
|
print("debug: " + some_info) # Don't use plain print()
|
||||||
|
|
||||||
|
# Use the global debug and logging systems
|
||||||
|
if DebugManager.is_debug_enabled():
|
||||||
|
show_debug_info()
|
||||||
|
DebugManager.log_debug("Debug information: " + some_info, "MyComponent")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Learning Resources
|
||||||
|
|
||||||
|
### Godot-Specific
|
||||||
|
- [GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html)
|
||||||
|
- [Godot Best Practices](https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html)
|
||||||
|
- Project documentation in `docs/map.md`
|
||||||
|
|
||||||
|
### General Programming
|
||||||
|
- Clean Code principles
|
||||||
|
- SOLID principles (adapted for game development)
|
||||||
|
- Git best practices
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
### When Stuck
|
||||||
|
1. Check existing code for similar patterns
|
||||||
|
2. Review project documentation (`docs/`)
|
||||||
|
3. Use the debug system to understand current state
|
||||||
|
4. Ask specific questions about project architecture
|
||||||
|
5. Test your understanding with small experiments
|
||||||
|
|
||||||
|
### Code Review Process
|
||||||
|
- Submit pull requests early and often
|
||||||
|
- Ask for feedback on approach before implementing large features
|
||||||
|
- Be open to suggestions and alternative approaches
|
||||||
|
- Learn from review feedback for future contributions
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This code of conduct emphasizes:
|
||||||
|
- **Clarity**: Write code that others can understand
|
||||||
|
- **Consistency**: Follow established patterns
|
||||||
|
- **Integration**: Use the project's systems properly
|
||||||
|
- **Learning**: Continuously improve your skills
|
||||||
|
|
||||||
|
Remember: It's better to ask questions and write clear, simple code than to guess and create complex, hard-to-maintain solutions. The goal is to contribute effectively to a codebase that will grow and evolve over time.
|
||||||
383
docs/DEVELOPMENT.md
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
# Development Guide
|
||||||
|
|
||||||
|
Development workflows, commands, and procedures for the Skelly project.
|
||||||
|
|
||||||
|
**Quick Links**:
|
||||||
|
- **Architecture**: [ARCHITECTURE.md](ARCHITECTURE.md) - Understand system design before developing
|
||||||
|
- **Coding Standards**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Follow coding conventions
|
||||||
|
- **Testing**: [TESTING.md](TESTING.md) - Testing procedures and protocols
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Running the Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Open project in Godot Editor
|
||||||
|
# Import project.godot
|
||||||
|
|
||||||
|
# Run project
|
||||||
|
# Press F5 in Godot Editor or use "Play" button
|
||||||
|
|
||||||
|
# Toggle debug UI in-game
|
||||||
|
# Press F12 key or use debug button
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Maps Generation
|
||||||
|
|
||||||
|
Generate machine-readable code intelligence and visual documentation for development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate everything (default behavior - maps + diagrams + docs + metrics)
|
||||||
|
python tools/generate_code_map.py
|
||||||
|
|
||||||
|
# Generate specific outputs
|
||||||
|
python tools/generate_code_map.py --diagrams # Visual diagrams only
|
||||||
|
python tools/generate_code_map.py --docs # Markdown documentation only
|
||||||
|
python tools/generate_code_map.py --metrics # Metrics dashboard only
|
||||||
|
|
||||||
|
# Generate all explicitly (same as no arguments)
|
||||||
|
python tools/generate_code_map.py --all
|
||||||
|
|
||||||
|
# Via development workflow tool
|
||||||
|
python tools/run_development.py --codemap
|
||||||
|
|
||||||
|
# Custom output (single JSON file)
|
||||||
|
python tools/generate_code_map.py --output custom_map.json
|
||||||
|
|
||||||
|
# Skip PNG rendering (Mermaid source only)
|
||||||
|
python tools/generate_code_map.py --diagrams --no-render
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generated Files** (Git-ignored):
|
||||||
|
|
||||||
|
**JSON Maps** (`.llm/` directory):
|
||||||
|
- `code_map_api.json` - Function signatures, parameters, return types
|
||||||
|
- `code_map_architecture.json` - Autoloads, design patterns, structure
|
||||||
|
- `code_map_flows.json` - Signal chains, scene transitions
|
||||||
|
- `code_map_security.json` - Input validation, error handling
|
||||||
|
- `code_map_assets.json` - Asset dependencies, licensing
|
||||||
|
- `code_map_metadata.json` - Statistics, quality metrics
|
||||||
|
|
||||||
|
**Diagrams** (`.llm/diagrams/` directory):
|
||||||
|
- `architecture.png` - Autoload system dependencies
|
||||||
|
- `signal_flows.png` - Signal connections between components
|
||||||
|
- `scene_hierarchy.png` - Scene tree structure
|
||||||
|
- `dependency_graph.png` - Module dependencies
|
||||||
|
- `*.mmd` - Mermaid source files
|
||||||
|
|
||||||
|
**Documentation** (`docs/generated/` directory):
|
||||||
|
- `AUTOLOADS_API.md` - Complete autoload API reference with diagrams
|
||||||
|
- `SIGNALS_CATALOG.md` - Signal catalog with flow diagrams
|
||||||
|
- `FUNCTION_INDEX.md` - Searchable function reference
|
||||||
|
- `SCENE_REFERENCE.md` - Scene hierarchy with diagrams
|
||||||
|
- `TODO_LIST.md` - Extracted TODO/FIXME comments
|
||||||
|
- `METRICS.md` - Project metrics dashboard with charts
|
||||||
|
|
||||||
|
**When to Regenerate**:
|
||||||
|
- Before LLM-assisted development sessions (fresh context)
|
||||||
|
- After adding new files or major refactoring (update structure)
|
||||||
|
- After changing autoloads or core architecture (capture changes)
|
||||||
|
- When onboarding new developers (comprehensive docs)
|
||||||
|
- To track project metrics and code complexity
|
||||||
|
|
||||||
|
### Testing Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run specific test
|
||||||
|
godot --headless --script tests/TestLogging.gd
|
||||||
|
|
||||||
|
# Run all save system tests
|
||||||
|
godot --headless --script tests/test_checksum_issue.gd
|
||||||
|
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||||
|
godot --headless --script tests/test_save_system_integration.gd
|
||||||
|
|
||||||
|
# Development workflow tool
|
||||||
|
python tools/run_development.py --test
|
||||||
|
```
|
||||||
|
|
||||||
|
See [TESTING.md](TESTING.md) for complete testing procedures.
|
||||||
|
|
||||||
|
## Development Workflows
|
||||||
|
|
||||||
|
### Adding a New Feature
|
||||||
|
|
||||||
|
1. **Generate code maps** for LLM context:
|
||||||
|
```bash
|
||||||
|
python tools/generate_code_map.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Review architecture** to understand system design:
|
||||||
|
- Read [ARCHITECTURE.md](ARCHITECTURE.md) for relevant system
|
||||||
|
- Understand autoload responsibilities
|
||||||
|
- Review existing patterns
|
||||||
|
|
||||||
|
3. **Follow coding standards**:
|
||||||
|
- Use [naming conventions](CODE_OF_CONDUCT.md#naming-convention-quick-reference)
|
||||||
|
- Follow [core patterns](CODE_OF_CONDUCT.md#project-specific-guidelines)
|
||||||
|
- Implement [input validation](CODE_OF_CONDUCT.md#input-validation-standards)
|
||||||
|
- Use [proper memory management](CODE_OF_CONDUCT.md#memory-management-standards)
|
||||||
|
|
||||||
|
4. **Write tests** for new functionality:
|
||||||
|
- Create test file in `tests/` directory
|
||||||
|
- Follow [test structure guidelines](TESTING.md#adding-new-tests)
|
||||||
|
- Run tests to verify functionality
|
||||||
|
|
||||||
|
5. **Check quality checklist** before committing:
|
||||||
|
- Review [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist)
|
||||||
|
- Verify all checkboxes are satisfied
|
||||||
|
- Run tests one more time
|
||||||
|
|
||||||
|
### Debugging Issues
|
||||||
|
|
||||||
|
1. **Toggle debug UI** with F12 in-game:
|
||||||
|
- View system state in debug panels
|
||||||
|
- Check current values and configurations
|
||||||
|
- Use debug controls for testing
|
||||||
|
|
||||||
|
2. **Check logs** using structured logging:
|
||||||
|
```gdscript
|
||||||
|
DebugManager.log_debug("Detailed debug info", "MyComponent")
|
||||||
|
DebugManager.log_info("Important event occurred", "MyComponent")
|
||||||
|
DebugManager.log_warn("Unexpected situation", "MyComponent")
|
||||||
|
DebugManager.log_error("Something failed", "MyComponent")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify state** in debug panels:
|
||||||
|
- Match-3: Check gem pool, grid state, match detection
|
||||||
|
- Settings: Verify volume, language, difficulty values
|
||||||
|
- Save: Check save data integrity
|
||||||
|
|
||||||
|
4. **Run relevant test scripts**:
|
||||||
|
```bash
|
||||||
|
# Test specific system
|
||||||
|
godot --headless --script tests/TestMatch3Gameplay.gd
|
||||||
|
godot --headless --script tests/TestSettingsManager.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Use Godot debugger**:
|
||||||
|
- Set breakpoints in code
|
||||||
|
- Inspect variables during execution
|
||||||
|
- Step through code execution
|
||||||
|
- Use remote inspector for live debugging
|
||||||
|
|
||||||
|
### Modifying Core Systems
|
||||||
|
|
||||||
|
1. **Understand current design**:
|
||||||
|
- Read [ARCHITECTURE.md](ARCHITECTURE.md) for system architecture
|
||||||
|
- Review autoload responsibilities
|
||||||
|
- Understand signal flows and dependencies
|
||||||
|
|
||||||
|
2. **Run existing tests first**:
|
||||||
|
```bash
|
||||||
|
# Save system testing
|
||||||
|
godot --headless --script tests/test_checksum_issue.gd
|
||||||
|
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||||
|
|
||||||
|
# Settings testing
|
||||||
|
godot --headless --script tests/TestSettingsManager.gd
|
||||||
|
|
||||||
|
# Game manager testing
|
||||||
|
godot --headless --script tests/TestGameManager.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Make changes following standards**:
|
||||||
|
- Follow [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) patterns
|
||||||
|
- Maintain backward compatibility where possible
|
||||||
|
- Add input validation and error handling
|
||||||
|
- Use proper memory management
|
||||||
|
|
||||||
|
4. **Run tests again** to verify no regressions:
|
||||||
|
```bash
|
||||||
|
# Run all relevant test suites
|
||||||
|
python tools/run_development.py --test
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Update documentation** if architecture changes:
|
||||||
|
- Update [ARCHITECTURE.md](ARCHITECTURE.md) if design patterns change
|
||||||
|
- Update [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) if new patterns emerge
|
||||||
|
- Update [TESTING.md](TESTING.md) if new test protocols needed
|
||||||
|
|
||||||
|
## Testing Changes
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run project with F5 in Godot Editor
|
||||||
|
# Test the following:
|
||||||
|
|
||||||
|
# Basic functionality
|
||||||
|
# - F12 toggle for debug UI
|
||||||
|
# - Scene transitions work correctly
|
||||||
|
# - Settings persist across restarts
|
||||||
|
# - Audio plays correctly
|
||||||
|
|
||||||
|
# Gameplay testing
|
||||||
|
# - Match-3: Gem swapping, match detection, scoring
|
||||||
|
# - Input: Keyboard (WASD/Arrows + Enter) and gamepad (D-pad + A)
|
||||||
|
# - Visual feedback: Selection highlights, animations
|
||||||
|
|
||||||
|
# Mobile compatibility (if UI changes made)
|
||||||
|
# - Touch input works
|
||||||
|
# - UI scales correctly on different resolutions
|
||||||
|
# - Performance is acceptable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automated Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Logging system
|
||||||
|
godot --headless --script tests/TestLogging.gd
|
||||||
|
|
||||||
|
# Save system (after SaveManager changes)
|
||||||
|
godot --headless --script tests/test_checksum_issue.gd
|
||||||
|
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||||
|
godot --headless --script tests/test_save_system_integration.gd
|
||||||
|
|
||||||
|
# Settings system
|
||||||
|
godot --headless --script tests/TestSettingsManager.gd
|
||||||
|
|
||||||
|
# Game manager
|
||||||
|
godot --headless --script tests/TestGameManager.gd
|
||||||
|
|
||||||
|
# Gameplay
|
||||||
|
godot --headless --script tests/TestMatch3Gameplay.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
See [TESTING.md](TESTING.md) for complete testing protocols.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### When Stuck
|
||||||
|
|
||||||
|
1. **Check system design**: Read [ARCHITECTURE.md](ARCHITECTURE.md) for understanding
|
||||||
|
2. **Review coding patterns**: Check [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for examples
|
||||||
|
3. **Use debug system**: Press F12 to inspect current state
|
||||||
|
4. **Search existing code**: Look for similar patterns in the codebase
|
||||||
|
5. **Test understanding**: Create small experiments to verify assumptions
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### Scene Loading Fails
|
||||||
|
**Problem**: Scene transitions not working or crashes during scene change
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
- Check if using `GameManager.start_game_with_mode()` (correct)
|
||||||
|
- Never use `get_tree().change_scene_to_file()` directly
|
||||||
|
- Verify scene paths are correct constants in GameManager
|
||||||
|
- Check for race conditions (multiple rapid scene changes)
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct
|
||||||
|
GameManager.start_game_with_mode("match3")
|
||||||
|
|
||||||
|
# ❌ Wrong
|
||||||
|
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logs Not Appearing
|
||||||
|
**Problem**: Debug messages not showing in console
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
- Use `DebugManager.log_*()` functions instead of `print()`
|
||||||
|
- Include category for log organization
|
||||||
|
- Check log level filtering (TRACE/DEBUG require debug mode)
|
||||||
|
- Verify DebugManager is configured as autoload
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct
|
||||||
|
DebugManager.log_info("Scene loaded", "GameManager")
|
||||||
|
DebugManager.log_error("Failed to load resource", "AssetLoader")
|
||||||
|
|
||||||
|
# ❌ Wrong
|
||||||
|
print("Scene loaded") # Use structured logging
|
||||||
|
push_error("Failed") # Missing context and category
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Memory Crashes
|
||||||
|
**Problem**: Crashes related to freed nodes or memory access
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
- Always use `queue_free()` instead of `free()`
|
||||||
|
- Wait for frame completion after queueing nodes
|
||||||
|
- Clear references before cleanup
|
||||||
|
- Check for access to freed objects
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```gdscript
|
||||||
|
# ✅ Correct
|
||||||
|
for child in children_to_remove:
|
||||||
|
child.queue_free()
|
||||||
|
await get_tree().process_frame # Wait for cleanup
|
||||||
|
|
||||||
|
# ❌ Wrong
|
||||||
|
for child in children_to_remove:
|
||||||
|
child.free() # Immediate deletion causes crashes
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tests Failing
|
||||||
|
**Problem**: Test scripts fail or hang
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
- Run tests in sequence (avoid parallel execution)
|
||||||
|
- Check autoload configuration in project.godot
|
||||||
|
- Remove existing save files before testing SaveManager
|
||||||
|
- Verify test file permissions
|
||||||
|
- Check test timeout (should complete within seconds)
|
||||||
|
|
||||||
|
#### Input Not Working
|
||||||
|
**Problem**: Keyboard/gamepad input not responding
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
- Verify input actions defined in project.godot
|
||||||
|
- Check input map configuration
|
||||||
|
- Ensure scene has input focus
|
||||||
|
- Test with both keyboard and gamepad
|
||||||
|
- Check for input event consumption by UI elements
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Before Committing
|
||||||
|
|
||||||
|
1. **Run all relevant tests**:
|
||||||
|
```bash
|
||||||
|
python tools/run_development.py --test
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check quality checklist**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist)
|
||||||
|
|
||||||
|
3. **Verify no regressions**: Test existing functionality
|
||||||
|
|
||||||
|
4. **Update documentation**: If patterns or architecture changed
|
||||||
|
|
||||||
|
5. **Review changes**: Use `git diff` to review all modifications
|
||||||
|
|
||||||
|
### Code Review
|
||||||
|
|
||||||
|
- Focus on code clarity and maintainability
|
||||||
|
- Check adherence to project patterns
|
||||||
|
- Verify input validation and error handling
|
||||||
|
- Ensure memory management is safe
|
||||||
|
- Test the changes yourself before approving
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
|
||||||
|
- Profile critical code paths
|
||||||
|
- Use object pooling for frequently created nodes
|
||||||
|
- Optimize expensive calculations
|
||||||
|
- Cache node references with `@onready`
|
||||||
|
- Avoid searching nodes repeatedly in `_process()`
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- [Godot GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html)
|
||||||
|
- [Godot Best Practices](https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html)
|
||||||
|
- [Godot Debugger](https://docs.godotengine.org/en/stable/tutorials/scripting/debug/overview_of_debugging_tools.html)
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [ARCHITECTURE.md](ARCHITECTURE.md) - System design and patterns
|
||||||
|
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Coding standards
|
||||||
|
- [TESTING.md](TESTING.md) - Testing procedures
|
||||||
|
- [UI_COMPONENTS.md](UI_COMPONENTS.md) - Component API reference
|
||||||
404
docs/MAP.md
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
# Skelly - Project Structure Map
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Skelly is a Godot 4.4 game project featuring multiple gameplay modes. The project supports match-3 puzzle gameplay with planned clickomania gameplay through a modular gameplay architecture. It follows a modular structure with clear separation between scenes, autoloads, assets, and data.
|
||||||
|
|
||||||
|
> 📋 **Naming Conventions**: All file and directory naming follows the standards defined in [Naming Convention Quick Reference](CODE_OF_CONDUCT.md#naming-convention-quick-reference).
|
||||||
|
|
||||||
|
## Project Root Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
skelly/
|
||||||
|
├── .claude/ # Claude Code configuration
|
||||||
|
├── .godot/ # Godot engine generated files (ignored)
|
||||||
|
├── assets/ # Game assets (sprites, audio, textures)
|
||||||
|
├── data/ # Game data and configuration files
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── localization/ # Internationalization files
|
||||||
|
├── scenes/ # Godot scenes (.tscn) and scripts (.gd)
|
||||||
|
├── src/ # Source code organization
|
||||||
|
├── tests/ # Test scripts and validation utilities
|
||||||
|
├── project.godot # Main Godot project configuration
|
||||||
|
└── icon.svg # Project icon
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Architecture
|
||||||
|
|
||||||
|
### Autoloads (Global Singletons)
|
||||||
|
Located in `src/autoloads/`, these scripts are automatically loaded when the game starts:
|
||||||
|
|
||||||
|
1. **SaveManager** (`src/autoloads/SaveManager.gd`)
|
||||||
|
- Persistent game data management with validation
|
||||||
|
- High score tracking and current score management
|
||||||
|
- Game statistics (games played, total score)
|
||||||
|
- Grid state persistence for match-3 gameplay continuity
|
||||||
|
- Progress reset functionality with complete data cleanup
|
||||||
|
- Security features: backup system, checksum validation, file size limits
|
||||||
|
- Robust error handling with backup restoration capabilities
|
||||||
|
- Uses: `user://savegame.save` for persistent storage
|
||||||
|
|
||||||
|
2. **SettingsManager** (`src/autoloads/SettingsManager.gd`)
|
||||||
|
- Manages game settings and user preferences
|
||||||
|
- Configuration file I/O
|
||||||
|
- input validation
|
||||||
|
- JSON parsing
|
||||||
|
- Provides language selection functionality
|
||||||
|
- Dependencies: `localization/languages.json`
|
||||||
|
|
||||||
|
3. **AudioManager** (`src/autoloads/AudioManager.gd`)
|
||||||
|
- Controls music and sound effects
|
||||||
|
- Manages audio bus configuration
|
||||||
|
- Uses: `data/default_bus_layout.tres`
|
||||||
|
|
||||||
|
4. **GameManager** (`src/autoloads/GameManager.gd`)
|
||||||
|
- Game state management and gameplay mode coordination with race condition protection
|
||||||
|
- Scene transitions with concurrent change prevention and validation
|
||||||
|
- Gameplay mode selection and launching with input validation (match3, clickomania)
|
||||||
|
- Error handling for scene loading failures and fallback mechanisms
|
||||||
|
- Navigation flow control with state protection
|
||||||
|
- References: main.tscn, game.tscn and individual gameplay scenes
|
||||||
|
|
||||||
|
5. **LocalizationManager** (`src/autoloads/LocalizationManager.gd`)
|
||||||
|
- Language switching functionality
|
||||||
|
- Works with Godot's built-in internationalization system
|
||||||
|
- Uses translation files in `localization/`
|
||||||
|
|
||||||
|
6. **DebugManager** (`src/autoloads/DebugManager.gd`)
|
||||||
|
- Global debug state management and centralized logging system
|
||||||
|
- Debug UI visibility control
|
||||||
|
- F12 toggle functionality
|
||||||
|
- Signal-based debug system
|
||||||
|
- Structured logging with configurable log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
|
||||||
|
- Timestamp-based log formatting with category support
|
||||||
|
- Runtime log level filtering
|
||||||
|
|
||||||
|
|
||||||
|
## Scene Hierarchy & Flow
|
||||||
|
|
||||||
|
### Main Scenes
|
||||||
|
```
|
||||||
|
main.tscn (Entry Point)
|
||||||
|
├── SplashScreen.tscn
|
||||||
|
├── MainMenu.tscn
|
||||||
|
└── SettingsMenu.tscn
|
||||||
|
|
||||||
|
game.tscn (Gameplay Container)
|
||||||
|
├── GameplayContainer (Dynamic content)
|
||||||
|
├── UI/ScoreDisplay
|
||||||
|
├── BackButton
|
||||||
|
└── DebugToggle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Game Flow
|
||||||
|
1. **Main Scene** (`scenes/main/Main.tscn` + `Main.gd`)
|
||||||
|
- Application entry point
|
||||||
|
- Manages splash screen
|
||||||
|
- Transitions to main menu
|
||||||
|
- Dynamic menu loading system
|
||||||
|
|
||||||
|
2. **Splash Screen** (`scenes/main/SplashScreen.tscn` + `SplashScreen.gd`)
|
||||||
|
- Initial splash screen
|
||||||
|
- Input detection for any key/button
|
||||||
|
- Signals to main scene for transition
|
||||||
|
|
||||||
|
3. **Main Menu** (`scenes/ui/MainMenu.tscn` + `MainMenu.gd`)
|
||||||
|
- Primary navigation hub
|
||||||
|
- Start game, settings, quit options
|
||||||
|
- Connected to GameManager for scene transitions
|
||||||
|
|
||||||
|
4. **Settings Menu** (`scenes/ui/SettingsMenu.tscn` + `SettingsMenu.gd`)
|
||||||
|
- User preferences interface
|
||||||
|
- Language selection
|
||||||
|
- Audio volume controls
|
||||||
|
- Connected to SettingsManager and AudioManager
|
||||||
|
|
||||||
|
5. **Game Scene** (`scenes/game/Game.tscn` + `Game.gd`)
|
||||||
|
- Main gameplay container with modular gameplay system
|
||||||
|
- Dynamic loading of gameplay modes into GameplayContainer
|
||||||
|
- Global score management and display
|
||||||
|
- Back button for navigation to main menu
|
||||||
|
- Gameplay mode switching support (Space+Enter debug key)
|
||||||
|
- Bridge between UI and individual gameplay implementations
|
||||||
|
|
||||||
|
### UI Components
|
||||||
|
```
|
||||||
|
scenes/ui/
|
||||||
|
├── components/
|
||||||
|
│ └── ValueStepper.tscn + ValueStepper.gd # Reusable arrow-based value selector
|
||||||
|
├── DebugToggle.tscn + DebugToggle.gd # Available on all major scenes
|
||||||
|
├── DebugMenuBase.gd # Unified base class for debug menus
|
||||||
|
├── DebugMenu.tscn + DebugMenu.gd # Global debug controls (extends DebugMenuBase)
|
||||||
|
├── Match3DebugMenu.gd # Match-3 specific debug controls (extends DebugMenuBase)
|
||||||
|
├── MainMenu.tscn + MainMenu.gd # With gamepad/keyboard navigation
|
||||||
|
└── SettingsMenu.tscn + SettingsMenu.gd # With comprehensive input validation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quality Improvements:**
|
||||||
|
- **ValueStepper Component**: Reusable arrow-based selector for discrete values (language, resolution, difficulty)
|
||||||
|
- **DebugMenuBase.gd**: Eliminates 90% code duplication between debug menu classes
|
||||||
|
- **Input Validation**: User inputs are validated and sanitized before processing
|
||||||
|
- **Error Recovery**: Error handling with fallback mechanisms throughout UI
|
||||||
|
- **Navigation Support**: Gamepad/keyboard navigation across menus
|
||||||
|
|
||||||
|
## Modular Gameplay System
|
||||||
|
|
||||||
|
The game now uses a modular gameplay architecture where different game modes can be dynamically loaded into the main game scene.
|
||||||
|
|
||||||
|
### Gameplay Architecture
|
||||||
|
- **Main Game Scene** (`scenes/game/Game.gd`) - Container and coordinator
|
||||||
|
- **Gameplay Directory** (`scenes/game/gameplays/`) - Individual gameplay implementations
|
||||||
|
- **Dynamic Loading** - Gameplay scenes loaded at runtime based on mode selection
|
||||||
|
- **Signal-based Communication** - Gameplays communicate with main scene via signals
|
||||||
|
|
||||||
|
### Current Gameplay Modes
|
||||||
|
|
||||||
|
#### Match-3 Mode (`scenes/game/gameplays/Match3Gameplay.tscn`)
|
||||||
|
1. **Match3 Controller** (`scenes/game/gameplays/Match3Gameplay.gd`)
|
||||||
|
- Grid management (8x8 default) with memory-safe node cleanup
|
||||||
|
- Match detection algorithms with bounds checking and validation
|
||||||
|
- Tile dropping and refilling with signal connections
|
||||||
|
- Gem pool management (3-8 gem types) with instance-based architecture
|
||||||
|
- Debug UI integration with validation
|
||||||
|
- Score reporting via `score_changed` signal
|
||||||
|
- **Memory Safety**: Uses `queue_free()` with frame waiting to prevent crashes
|
||||||
|
- **Gem Movement System**: Keyboard and gamepad input for tile selection and swapping
|
||||||
|
- State machine: WAITING → SELECTING → SWAPPING → PROCESSING
|
||||||
|
- Adjacent tile validation (horizontal/vertical neighbors only)
|
||||||
|
- Match validation (swaps must create matches or revert)
|
||||||
|
- Smooth tile position animations with Tween
|
||||||
|
- Cursor-based navigation with visual highlighting and bounds checking
|
||||||
|
|
||||||
|
2. **Tile System** (`scenes/game/gameplays/Tile.gd` + `Tile.tscn`)
|
||||||
|
- Tile behavior with instance-based architecture (no global state)
|
||||||
|
- Gem type management with validation and bounds checking
|
||||||
|
- Visual representation with scaling and color
|
||||||
|
- Group membership for coordination
|
||||||
|
- **Visual Feedback System**: Multi-state display for game interaction
|
||||||
|
- Selection visual feedback (scale and color modulation)
|
||||||
|
- State management (normal, highlighted, selected)
|
||||||
|
- Signal-based communication with gameplay controller
|
||||||
|
- Smooth animations with Tween system
|
||||||
|
- **Memory Safety**: Resource management and cleanup
|
||||||
|
|
||||||
|
#### Clickomania Mode (`scenes/game/gameplays/ClickomaniaGameplay.tscn`)
|
||||||
|
- Planned implementation for clickomania-style gameplay
|
||||||
|
- Will integrate with same scoring and UI systems as match-3
|
||||||
|
|
||||||
|
### Debug System
|
||||||
|
- Global debug state via DebugManager with initialization
|
||||||
|
- Debug toggle available on all major scenes (MainMenu, SettingsMenu, SplashScreen, Game)
|
||||||
|
- Match-3 specific debug UI panel with gem count controls and difficulty presets
|
||||||
|
- Gem count controls (+/- buttons) with difficulty presets (Easy: 3, Normal: 5, Hard: 8)
|
||||||
|
- Board reroll functionality for testing
|
||||||
|
- F12 toggle support across all scenes
|
||||||
|
- Fewer debug prints in production code
|
||||||
|
|
||||||
|
## Asset Organization
|
||||||
|
|
||||||
|
### Audio (`assets/audio/`)
|
||||||
|
```
|
||||||
|
audio/
|
||||||
|
├── music/ # Background music files
|
||||||
|
└── sfx/ # Sound effects
|
||||||
|
```
|
||||||
|
|
||||||
|
### Visual Assets (`assets/`)
|
||||||
|
```
|
||||||
|
assets/
|
||||||
|
├── audio/
|
||||||
|
│ ├── music/ # Background music files
|
||||||
|
│ └── sfx/ # Sound effects
|
||||||
|
├── sprites/
|
||||||
|
│ ├── characters/skeleton/ # Character artwork
|
||||||
|
│ ├── gems/ # Match-3 gem sprites
|
||||||
|
│ └── ui/ # User interface elements
|
||||||
|
├── textures/
|
||||||
|
│ └── backgrounds/ # Background images
|
||||||
|
├── fonts/ # Custom fonts
|
||||||
|
└── sources.yaml # Asset metadata and attribution
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asset Management (`assets/sources.yaml`)
|
||||||
|
**REQUIRED**: Every asset added to the project must be documented in `assets/sources.yaml` with:
|
||||||
|
- **Source information** - Where the asset came from (URL, artist, store, etc.)
|
||||||
|
- **License details** - Usage rights, attribution requirements, commercial permissions
|
||||||
|
- **Attribution text** - Exact text to use for credits if required
|
||||||
|
- **Modification notes** - Any changes made to the original asset
|
||||||
|
- **Usage context** - Where and how the asset is used in the project
|
||||||
|
|
||||||
|
**Example format:**
|
||||||
|
```yaml
|
||||||
|
audio:
|
||||||
|
music:
|
||||||
|
"Space Horror InGame Music (Exploration) _Clement Panchout.wav":
|
||||||
|
source: "https://freesound.org/people/ClementPanchout/"
|
||||||
|
license: "CC BY 4.0"
|
||||||
|
attribution: "Space Horror InGame Music by Clement Panchout"
|
||||||
|
modifications: "Converted to WAV, loop points adjusted"
|
||||||
|
usage: "Background music for all gameplay scenes"
|
||||||
|
|
||||||
|
sprites:
|
||||||
|
gems:
|
||||||
|
"gem_blue.png":
|
||||||
|
source: "Created in-house"
|
||||||
|
license: "Project proprietary"
|
||||||
|
attribution: "Skelly development team"
|
||||||
|
modifications: "None"
|
||||||
|
usage: "Match-3 blue gem sprite"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data & Configuration
|
||||||
|
|
||||||
|
### Game Data (`data/`)
|
||||||
|
- `default_bus_layout.tres` - Audio bus configuration for Godot
|
||||||
|
|
||||||
|
### Documentation (`docs/`)
|
||||||
|
- `MAP.md` - Project architecture and structure overview
|
||||||
|
- `CLAUDE.md` - Claude Code development guidelines
|
||||||
|
- `CODE_OF_CONDUCT.md` - Coding standards and best practices
|
||||||
|
- `TESTING.md` - Testing guidelines and conventions
|
||||||
|
|
||||||
|
### Localization (`localization/`)
|
||||||
|
- `languages.json` - Available language definitions
|
||||||
|
- `MainStrings.en.translation` - English translations
|
||||||
|
- `MainStrings.ru.translation` - Russian translations
|
||||||
|
|
||||||
|
### Testing & Validation (`tests/`)
|
||||||
|
- `TestLogging.gd` - DebugManager logging system validation
|
||||||
|
- **`test_checksum_issue.gd`** - SaveManager checksum validation and deterministic hashing
|
||||||
|
- **`TestMigrationCompatibility.gd`** - SaveManager version migration and backward compatibility
|
||||||
|
- **`test_save_system_integration.gd`** - Complete save/load workflow integration testing
|
||||||
|
- **`test_checksum_fix_verification.gd`** - JSON serialization checksum fix verification
|
||||||
|
- `README.md` - Brief directory overview (see docs/TESTING.md for full guidelines)
|
||||||
|
- Comprehensive test scripts for save system security and data integrity validation
|
||||||
|
- Temporary test utilities for development and debugging
|
||||||
|
|
||||||
|
### Project Configuration
|
||||||
|
- `project.godot` - Main Godot project settings
|
||||||
|
- Autoload definitions
|
||||||
|
- Input map configurations
|
||||||
|
- Rendering settings
|
||||||
|
- Audio bus references
|
||||||
|
|
||||||
|
## Key Dependencies & Connections
|
||||||
|
|
||||||
|
### Signal Connections
|
||||||
|
```
|
||||||
|
SplashScreen --[confirm_pressed]--> Main
|
||||||
|
MainMenu --[open_settings]--> Main
|
||||||
|
SettingsMenu --[back_to_main_menu]--> Main
|
||||||
|
DebugManager --[debug_toggled]--> All scenes with DebugToggle
|
||||||
|
GameplayModes --[score_changed]--> Game Scene
|
||||||
|
Game Scene --[score updates]--> UI/ScoreDisplay
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scene References
|
||||||
|
```
|
||||||
|
GameManager --> main.tscn, game.tscn
|
||||||
|
GameManager --> scenes/game/gameplays/*.tscn (via GAMEPLAY_SCENES constant)
|
||||||
|
Main --> MainMenu.tscn, SettingsMenu.tscn
|
||||||
|
Game --> GameplayContainer (dynamic loading of gameplay scenes)
|
||||||
|
Game --> scenes/game/gameplays/Match3Gameplay.tscn, ClickomaniaGameplay.tscn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asset Dependencies
|
||||||
|
```
|
||||||
|
AudioManager --> assets/audio/music/
|
||||||
|
SettingsManager --> localization/languages.json
|
||||||
|
AudioManager --> data/default_bus_layout.tres
|
||||||
|
Asset Management --> assets/sources.yaml (required for all assets)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging System
|
||||||
|
|
||||||
|
The project uses a centralized logging system implemented in DebugManager for consistent, structured logging throughout the application.
|
||||||
|
|
||||||
|
### Log Levels
|
||||||
|
```
|
||||||
|
TRACE (0) - Detailed execution tracing (debug mode only)
|
||||||
|
DEBUG (1) - Development debugging information (debug mode only)
|
||||||
|
INFO (2) - General application information (always visible)
|
||||||
|
WARN (3) - Warning messages that don't break functionality
|
||||||
|
ERROR (4) - Error conditions that may affect functionality
|
||||||
|
FATAL (5) - Critical errors that may cause application failure
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Examples
|
||||||
|
```gdscript
|
||||||
|
# Basic logging with automatic categorization
|
||||||
|
DebugManager.log_info("Game started successfully")
|
||||||
|
DebugManager.log_warn("Settings file not found, using defaults")
|
||||||
|
DebugManager.log_error("Failed to load audio resource")
|
||||||
|
|
||||||
|
# Logging with custom categories for better organization
|
||||||
|
DebugManager.log_debug("Grid regenerated with 64 tiles", "Match3")
|
||||||
|
DebugManager.log_info("Language changed to: en", "Settings")
|
||||||
|
DebugManager.log_error("Invalid scene path provided", "GameManager")
|
||||||
|
|
||||||
|
# Standard categories in use:
|
||||||
|
# - GameManager: Scene transitions, gameplay mode management
|
||||||
|
# - Match3: Match-3 gameplay, grid operations, tile interactions
|
||||||
|
# - Settings: Settings management, language changes
|
||||||
|
# - Game: Main game scene, mode switching
|
||||||
|
# - MainMenu: Main menu interactions
|
||||||
|
# - SplashScreen: Splash screen
|
||||||
|
# - Clickomania: Clickomania gameplay mode
|
||||||
|
# - DebugMenu: Debug menu operations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Format
|
||||||
|
```
|
||||||
|
[timestamp] LEVEL [category]: message
|
||||||
|
Example: [2025-09-24T10:48:08] INFO [GameManager]: Loading main scene
|
||||||
|
```
|
||||||
|
|
||||||
|
### Runtime Configuration
|
||||||
|
```gdscript
|
||||||
|
# Set minimum log level (filters out lower priority messages)
|
||||||
|
DebugManager.set_log_level(DebugManager.LogLevel.WARN)
|
||||||
|
|
||||||
|
# Get current log level
|
||||||
|
var current_level = DebugManager.get_log_level()
|
||||||
|
|
||||||
|
# Check if a specific level would be logged
|
||||||
|
if DebugManager._should_log(DebugManager.LogLevel.DEBUG):
|
||||||
|
# Expensive debug calculation here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration with Godot Systems
|
||||||
|
- **WARN/ERROR/FATAL** levels automatically call `push_warning()` and `push_error()`
|
||||||
|
- **TRACE/DEBUG** levels only display when debug mode is enabled
|
||||||
|
- **INFO** and higher levels always display regardless of debug mode
|
||||||
|
- All levels respect the configured minimum log level threshold
|
||||||
|
|
||||||
|
## Development Notes
|
||||||
|
|
||||||
|
### Current Implementation Status
|
||||||
|
- Modular gameplay system with dynamic loading architecture
|
||||||
|
- Match-3 system with 8x8 grid and configurable gem pools
|
||||||
|
- **Interactive Match-3 Gameplay**: Keyboard and gamepad gem movement system
|
||||||
|
- Keyboard: Arrow key navigation with Enter to select/confirm (WASD also supported)
|
||||||
|
- Gamepad: D-pad navigation with A button to select/confirm
|
||||||
|
- Visual feedback: Tile highlighting, selection indicators, smooth animations
|
||||||
|
- Game logic: Adjacent-only swaps, match validation, automatic revert on invalid moves
|
||||||
|
- State machine: WAITING → SELECTING → SWAPPING → PROCESSING states
|
||||||
|
- **Comprehensive Logging System**: All print()/push_error() statements migrated to DebugManager
|
||||||
|
- Structured logging with categories: GameManager, Match3, Settings, Game, MainMenu, etc.
|
||||||
|
- Multiple log levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
|
||||||
|
- Debug mode integration with level filtering
|
||||||
|
- Global scoring system integrated across gameplay modes
|
||||||
|
- Debug UI system with F12 toggle functionality across all major scenes
|
||||||
|
- Scene transition system via GameManager with gameplay mode support
|
||||||
|
- Internationalization support for English/Russian
|
||||||
|
|
||||||
|
### Architecture Patterns
|
||||||
|
1. **Autoload Pattern** - Global managers as singletons
|
||||||
|
2. **Signal-Based Communication** - Loose coupling between components
|
||||||
|
3. **Modular Gameplay Architecture** - Dynamic loading of gameplay modes
|
||||||
|
4. **Scene Composition** - Modular scene loading and management
|
||||||
|
5. **Data-Driven Configuration** - JSON for settings and translations
|
||||||
|
6. **Component Architecture** - Reusable UI and game components
|
||||||
|
7. **Centralized Scoring System** - Global score management across gameplay modes
|
||||||
|
8. **Structured Logging System** - Centralized logging with level-based filtering and formatted output
|
||||||
|
|
||||||
|
This structure provides a clean separation of concerns, making the codebase maintainable and extensible for future features.
|
||||||
277
docs/TESTING.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Tests Directory
|
||||||
|
|
||||||
|
Test scripts and utilities for validating Skelly project systems.
|
||||||
|
|
||||||
|
**Related Documentation**:
|
||||||
|
- **Coding Standards**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Follow coding standards when writing tests
|
||||||
|
- **Architecture**: [ARCHITECTURE.md](ARCHITECTURE.md) - Understand system design before testing
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `tests/` directory contains:
|
||||||
|
- System validation scripts
|
||||||
|
- Component testing utilities
|
||||||
|
- Integration tests
|
||||||
|
- Performance benchmarks
|
||||||
|
- Debugging tools
|
||||||
|
|
||||||
|
> 📋 **File Naming**: All test files follow the [naming conventions](CODE_OF_CONDUCT.md#naming-convention-quick-reference) with PascalCase and "Test" prefix (e.g., `TestAudioManager.gd`).
|
||||||
|
|
||||||
|
## Current Test Files
|
||||||
|
|
||||||
|
### `TestLogging.gd`
|
||||||
|
Test script for DebugManager logging system.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Tests all log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
|
||||||
|
- Validates log level filtering
|
||||||
|
- Tests category-based logging
|
||||||
|
- Verifies debug mode integration
|
||||||
|
- Demonstrates logging usage patterns
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```gdscript
|
||||||
|
# Option 1: Add as temporary autoload
|
||||||
|
# In project.godot, add: tests/TestLogging.gd
|
||||||
|
|
||||||
|
# Option 2: Instantiate in a scene
|
||||||
|
var test_script = preload("res://tests/TestLogging.gd").new()
|
||||||
|
add_child(test_script)
|
||||||
|
|
||||||
|
# Option 3: Run directly from editor
|
||||||
|
# Open the script and run the scene containing it
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Output:**
|
||||||
|
Formatted log messages showing:
|
||||||
|
- Timestamp formatting
|
||||||
|
- Log level filtering
|
||||||
|
- Category organization
|
||||||
|
- Debug mode dependency for TRACE/DEBUG levels
|
||||||
|
|
||||||
|
## Adding New Tests
|
||||||
|
|
||||||
|
Follow these conventions for new test files:
|
||||||
|
|
||||||
|
### File Naming
|
||||||
|
- Use descriptive names starting with `test_`
|
||||||
|
- Example: `TestAudioManager.gd`, `test_scene_transitions.gd`
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
```gdscript
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
# Brief description of what this test validates
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# Wait for system initialization if needed
|
||||||
|
await get_tree().process_frame
|
||||||
|
run_tests()
|
||||||
|
|
||||||
|
func run_tests():
|
||||||
|
print("=== Starting [System Name] Tests ===")
|
||||||
|
|
||||||
|
# Individual test functions
|
||||||
|
test_basic_functionality()
|
||||||
|
test_edge_cases()
|
||||||
|
test_error_conditions()
|
||||||
|
|
||||||
|
print("=== [System Name] Tests Complete ===")
|
||||||
|
|
||||||
|
func test_basic_functionality():
|
||||||
|
print("\\n--- Test: Basic Functionality ---")
|
||||||
|
# Test implementation
|
||||||
|
|
||||||
|
func test_edge_cases():
|
||||||
|
print("\\n--- Test: Edge Cases ---")
|
||||||
|
# Edge case testing
|
||||||
|
|
||||||
|
func test_error_conditions():
|
||||||
|
print("\\n--- Test: Error Conditions ---")
|
||||||
|
# Error condition testing
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Guidelines
|
||||||
|
|
||||||
|
1. **Independence**: Each test is self-contained
|
||||||
|
2. **Cleanup**: Restore original state after testing
|
||||||
|
3. **Clear Output**: Use descriptive print statements
|
||||||
|
4. **Error Handling**: Test success and failure conditions
|
||||||
|
5. **Documentation**: Comment complex test scenarios
|
||||||
|
|
||||||
|
### Integration with Main Project
|
||||||
|
|
||||||
|
- **Temporary Usage**: Add test files temporarily during development
|
||||||
|
- **Not in Production**: Exclude from release builds
|
||||||
|
- **Autoload Testing**: Add to autoloads temporarily for automatic execution
|
||||||
|
- **Manual Testing**: Run individually for specific components
|
||||||
|
|
||||||
|
## Test Categories
|
||||||
|
|
||||||
|
### System Tests
|
||||||
|
Test core autoload managers and global systems:
|
||||||
|
- `TestLogging.gd` - DebugManager logging system
|
||||||
|
- `test_checksum_issue.gd` - SaveManager checksum validation and deterministic hashing
|
||||||
|
- `TestMigrationCompatibility.gd` - SaveManager version migration and backward compatibility
|
||||||
|
- `test_save_system_integration.gd` - Complete save/load workflow integration testing
|
||||||
|
- `test_checksum_fix_verification.gd` - Verification of JSON serialization checksum fixes
|
||||||
|
- `TestSettingsManager.gd` - SettingsManager security validation, input validation, and error handling
|
||||||
|
- `TestGameManager.gd` - GameManager scene transitions, race condition protection, and input validation
|
||||||
|
- `TestAudioManager.gd` - AudioManager functionality, resource loading, and volume management
|
||||||
|
|
||||||
|
### Component Tests
|
||||||
|
Test individual game components:
|
||||||
|
- `TestMatch3Gameplay.gd` - Match-3 gameplay mechanics, grid management, and match detection
|
||||||
|
- `TestTile.gd` - Tile component behavior, visual feedback, and memory safety
|
||||||
|
- `TestValueStepper.gd` - ValueStepper UI component functionality and settings integration
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
Test system interactions and workflows:
|
||||||
|
- Future: `test_game_flow.gd` - Complete game session flow
|
||||||
|
- Future: `test_debug_system.gd` - Debug UI integration
|
||||||
|
- Future: `test_localization.gd` - Language switching and translations
|
||||||
|
|
||||||
|
## Save System Testing Protocols
|
||||||
|
|
||||||
|
SaveManager implements security features requiring testing for modifications.
|
||||||
|
|
||||||
|
### Critical Test Suites
|
||||||
|
|
||||||
|
#### **`test_checksum_issue.gd`** - Checksum Validation
|
||||||
|
**Tests**: Checksum generation, JSON serialization consistency, save/load cycles
|
||||||
|
**Usage**: Run after checksum algorithm changes
|
||||||
|
|
||||||
|
#### **`TestMigrationCompatibility.gd`** - Version Migration
|
||||||
|
**Tests**: Backward compatibility, missing field addition, data structure normalization
|
||||||
|
**Usage**: Test save format upgrades
|
||||||
|
|
||||||
|
#### **`test_save_system_integration.gd`** - End-to-End Integration
|
||||||
|
**Tests**: Save/load workflow, grid state serialization, race condition prevention
|
||||||
|
**Usage**: Run after SaveManager modifications
|
||||||
|
|
||||||
|
#### **`test_checksum_fix_verification.gd`** - JSON Serialization Fix
|
||||||
|
**Tests**: Checksum consistency, int/float conversion, type safety validation
|
||||||
|
**Usage**: Test JSON type conversion fixes
|
||||||
|
|
||||||
|
### Save System Security Testing
|
||||||
|
|
||||||
|
#### **Required Tests Before SaveManager Changes**
|
||||||
|
1. Run 4 save system test suites
|
||||||
|
2. Test tamper detection by modifying save files
|
||||||
|
3. Validate error recovery by corrupting files
|
||||||
|
4. Check race condition protection
|
||||||
|
5. Verify permissive validation
|
||||||
|
|
||||||
|
#### **Performance Benchmarks**
|
||||||
|
- Checksum calculation: < 1ms
|
||||||
|
- Memory usage: File size limits prevent exhaustion
|
||||||
|
- Error recovery: Never crash regardless of corruption
|
||||||
|
- Data preservation: User scores survive migration
|
||||||
|
|
||||||
|
#### **Test Sequence After Modifications**
|
||||||
|
1. `test_checksum_issue.gd` - Verify checksum consistency
|
||||||
|
2. `TestMigrationCompatibility.gd` - Check version upgrades
|
||||||
|
3. `test_save_system_integration.gd` - Validate workflow
|
||||||
|
4. Manual testing with corrupted files
|
||||||
|
5. Performance validation
|
||||||
|
|
||||||
|
**Failure Response**: Test failure indicates corruption risk. Do not commit until all tests pass.
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
### Manual Test Execution
|
||||||
|
|
||||||
|
#### **Direct Script Execution (Recommended)**
|
||||||
|
```bash
|
||||||
|
# Run specific test
|
||||||
|
godot --headless --script tests/test_checksum_issue.gd
|
||||||
|
|
||||||
|
# Run all save system tests
|
||||||
|
godot --headless --script tests/test_checksum_issue.gd
|
||||||
|
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||||
|
godot --headless --script tests/test_save_system_integration.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Other Methods**
|
||||||
|
- **Temporary Autoload**: Add to project.godot autoloads temporarily, run with F5
|
||||||
|
- **Scene-based**: Create temporary scene, add test script as child, run with F6
|
||||||
|
- **Editor**: Open test file, attach to scene, run with F6
|
||||||
|
|
||||||
|
### Automated Test Execution
|
||||||
|
|
||||||
|
Use provided scripts `run_tests.bat` (Windows) or `run_tests.sh` (Linux/Mac) to run all tests sequentially.
|
||||||
|
|
||||||
|
For CI/CD integration:
|
||||||
|
```yaml
|
||||||
|
- name: Run Test Suite
|
||||||
|
run: |
|
||||||
|
godot --headless --script tests/test_checksum_issue.gd
|
||||||
|
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||||
|
# Add other tests as needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expected Test Output
|
||||||
|
|
||||||
|
#### **Successful Test Run:**
|
||||||
|
```
|
||||||
|
=== Testing Checksum Issue Fix ===
|
||||||
|
Testing checksum consistency across save/load cycles...
|
||||||
|
✅ SUCCESS: Checksums are deterministic
|
||||||
|
✅ SUCCESS: JSON serialization doesn't break checksums
|
||||||
|
✅ SUCCESS: Save/load cycle maintains checksum integrity
|
||||||
|
=== Test Complete ===
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Failed Test Run:**
|
||||||
|
```
|
||||||
|
=== Testing Checksum Issue Fix ===
|
||||||
|
Testing checksum consistency across save/load cycles...
|
||||||
|
❌ FAILURE: Checksum mismatch detected
|
||||||
|
Expected: 1234567890
|
||||||
|
Got: 9876543210
|
||||||
|
=== Test Failed ===
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Execution Best Practices
|
||||||
|
|
||||||
|
**Before**: Remove existing save files, verify autoloads configured, run one test at a time
|
||||||
|
**During**: Monitor console output, note timing (tests complete within seconds)
|
||||||
|
**After**: Clean up temporary files, document issues
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
**Common Issues:**
|
||||||
|
- Permission errors: Run with elevated permissions if needed
|
||||||
|
- Missing dependencies: Ensure autoloads configured
|
||||||
|
- Timeout issues: Add timeout for hung tests
|
||||||
|
- Path issues: Use absolute paths if relative paths fail
|
||||||
|
|
||||||
|
### Performance Benchmarks
|
||||||
|
|
||||||
|
Expected execution times: Individual tests < 5 seconds, total suite < 35 seconds.
|
||||||
|
|
||||||
|
If tests take longer, investigate file I/O issues, memory leaks, infinite loops, or external dependencies.
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. Document expected behavior
|
||||||
|
2. Test boundary conditions and edge cases
|
||||||
|
3. Measure performance for critical components
|
||||||
|
4. Include visual validation for UI components
|
||||||
|
5. Cleanup after tests
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
When adding test files:
|
||||||
|
1. Follow [naming conventions](CODE_OF_CONDUCT.md#naming-convention-quick-reference)
|
||||||
|
2. Follow [coding standards](CODE_OF_CONDUCT.md) for test code quality
|
||||||
|
3. Understand [system architecture](ARCHITECTURE.md) before writing integration tests
|
||||||
|
4. Update this file with test descriptions
|
||||||
|
5. Ensure tests are self-contained and documented
|
||||||
|
6. Test success and failure scenarios
|
||||||
|
|
||||||
|
This testing approach maintains code quality and provides validation tools for system changes.
|
||||||
|
|
||||||
|
**See Also**:
|
||||||
|
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist) - Quality checklist before committing
|
||||||
|
- [ARCHITECTURE.md](ARCHITECTURE.md) - System design and architectural patterns
|
||||||
281
docs/UI_COMPONENTS.md
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
# UI Components
|
||||||
|
|
||||||
|
This document describes the custom UI components available in the Skelly project.
|
||||||
|
|
||||||
|
## ValueStepper Component
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
**ValueStepper** is a reusable UI control for stepping through discrete values with left/right arrow navigation. It provides an intuitive interface for selecting from predefined options and is particularly well-suited for game settings menus.
|
||||||
|
|
||||||
|
**Location**: `scenes/ui/components/ValueStepper.tscn` and `scenes/ui/components/ValueStepper.gd`
|
||||||
|
|
||||||
|
### Why ValueStepper Exists
|
||||||
|
|
||||||
|
Godot's built-in controls have limitations for discrete option selection:
|
||||||
|
- **OptionButton**: Dropdown popups don't work well with gamepad navigation
|
||||||
|
- **SpinBox**: Designed for numeric values, not text options
|
||||||
|
- **HSlider**: Better for continuous values, not discrete choices
|
||||||
|
|
||||||
|
ValueStepper fills this gap by providing:
|
||||||
|
- ✅ **Gamepad-friendly** discrete option selection
|
||||||
|
- ✅ **No popup complications** - values displayed inline
|
||||||
|
- ✅ **Dual input support** - mouse clicks + keyboard/gamepad
|
||||||
|
- ✅ **Clean horizontal layout** with current value always visible
|
||||||
|
- ✅ **Perfect for game settings** like language, difficulty, resolution
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **Multiple Data Sources**: Built-in support for language, resolution, difficulty
|
||||||
|
- **Custom Values**: Easy setup with custom arrays of values
|
||||||
|
- **Navigation Integration**: Built-in highlighting and input handling
|
||||||
|
- **Signal-Based**: Clean event communication with parent scenes
|
||||||
|
- **Visual Feedback**: Automatic highlighting and animations
|
||||||
|
- **Audio Support**: Integrated click sounds
|
||||||
|
- **Flexible Display**: Separate display names and internal values
|
||||||
|
|
||||||
|
### Visual Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
[<] [Current Value] [>]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Left Arrow Button** (`<`): Navigate to previous value
|
||||||
|
- **Value Display**: Shows current selection (e.g., "English", "Hard", "1920×1080")
|
||||||
|
- **Right Arrow Button** (`>`): Navigate to next value
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Signals
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
signal value_changed(new_value: String, new_index: int)
|
||||||
|
```
|
||||||
|
Emitted when the value changes, providing both the new value string and its index.
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
@export var data_source: String = "language"
|
||||||
|
@export var custom_format_function: String = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
- **data_source**: Determines the data type ("language", "resolution", "difficulty", or "custom")
|
||||||
|
- **custom_format_function**: Reserved for future custom formatting (currently unused)
|
||||||
|
|
||||||
|
### Key Methods
|
||||||
|
|
||||||
|
#### Setup and Configuration
|
||||||
|
```gdscript
|
||||||
|
func setup_custom_values(custom_values: Array[String], custom_display_names: Array[String] = [])
|
||||||
|
```
|
||||||
|
Configure the stepper with custom values and optional display names.
|
||||||
|
|
||||||
|
#### Value Management
|
||||||
|
```gdscript
|
||||||
|
func get_current_value() -> String
|
||||||
|
func set_current_value(value: String)
|
||||||
|
func change_value(direction: int)
|
||||||
|
```
|
||||||
|
Get, set, or modify the current value programmatically.
|
||||||
|
|
||||||
|
#### Navigation Integration
|
||||||
|
```gdscript
|
||||||
|
func set_highlighted(highlighted: bool)
|
||||||
|
func handle_input_action(action: String) -> bool
|
||||||
|
func get_control_name() -> String
|
||||||
|
```
|
||||||
|
Integration methods for navigation systems and visual feedback.
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Basic Usage in Scene
|
||||||
|
|
||||||
|
1. **Add to Scene**: Instance `ValueStepper.tscn` in your scene
|
||||||
|
2. **Set Data Source**: Configure the `data_source` property
|
||||||
|
3. **Connect Signal**: Connect the `value_changed` signal
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# In your scene script
|
||||||
|
@onready var language_stepper: ValueStepper = $LanguageStepper
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
language_stepper.value_changed.connect(_on_language_changed)
|
||||||
|
|
||||||
|
func _on_language_changed(new_value: String, new_index: int):
|
||||||
|
print("Language changed to: ", new_value)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Built-in Data Sources
|
||||||
|
|
||||||
|
#### Language Selection
|
||||||
|
```gdscript
|
||||||
|
# Set data_source = "language" in editor or code
|
||||||
|
language_stepper.data_source = "language"
|
||||||
|
```
|
||||||
|
Automatically loads available languages from SettingsManager.
|
||||||
|
|
||||||
|
#### Resolution Selection
|
||||||
|
```gdscript
|
||||||
|
# Set data_source = "resolution"
|
||||||
|
resolution_stepper.data_source = "resolution"
|
||||||
|
```
|
||||||
|
Provides common resolution options with display names.
|
||||||
|
|
||||||
|
#### Difficulty Selection
|
||||||
|
```gdscript
|
||||||
|
# Set data_source = "difficulty"
|
||||||
|
difficulty_stepper.data_source = "difficulty"
|
||||||
|
```
|
||||||
|
Provides difficulty levels: Easy, Normal, Hard, Nightmare.
|
||||||
|
|
||||||
|
### Custom Values
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# Setup custom theme selector
|
||||||
|
var theme_values = ["light", "dark", "blue", "green"]
|
||||||
|
var theme_names = ["Light Theme", "Dark Theme", "Blue Theme", "Green Theme"]
|
||||||
|
theme_stepper.setup_custom_values(theme_values, theme_names)
|
||||||
|
theme_stepper.data_source = "theme" # For better logging
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation System Integration
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# In a navigation-enabled menu
|
||||||
|
var navigable_controls: Array[Control] = []
|
||||||
|
|
||||||
|
func _setup_navigation():
|
||||||
|
navigable_controls.append(volume_slider)
|
||||||
|
navigable_controls.append(language_stepper) # Add stepper to navigation
|
||||||
|
navigable_controls.append(back_button)
|
||||||
|
|
||||||
|
func _update_visual_selection():
|
||||||
|
for i in range(navigable_controls.size()):
|
||||||
|
var control = navigable_controls[i]
|
||||||
|
if control is ValueStepper:
|
||||||
|
control.set_highlighted(i == current_index)
|
||||||
|
else:
|
||||||
|
# Handle other control highlighting
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _handle_input(action: String):
|
||||||
|
var current_control = navigable_controls[current_index]
|
||||||
|
if current_control is ValueStepper:
|
||||||
|
if current_control.handle_input_action(action):
|
||||||
|
AudioManager.play_ui_click()
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Patterns
|
||||||
|
|
||||||
|
### Settings Menu Pattern
|
||||||
|
See `scenes/ui/SettingsMenu.gd` for a complete example of integrating ValueStepper into a settings menu with full navigation support.
|
||||||
|
|
||||||
|
### Multiple Steppers Navigation
|
||||||
|
See `examples/ValueStepperExample.gd` for an example showing multiple steppers with keyboard/gamepad navigation.
|
||||||
|
|
||||||
|
## Extending ValueStepper
|
||||||
|
|
||||||
|
### Adding New Data Sources
|
||||||
|
|
||||||
|
1. **Add to `_load_data()` method**:
|
||||||
|
```gdscript
|
||||||
|
func _load_data():
|
||||||
|
match data_source:
|
||||||
|
"language":
|
||||||
|
_load_language_data()
|
||||||
|
"your_custom_type":
|
||||||
|
_load_your_custom_data()
|
||||||
|
# ... other cases
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Implement your loader**:
|
||||||
|
```gdscript
|
||||||
|
func _load_your_custom_data():
|
||||||
|
values = ["value1", "value2", "value3"]
|
||||||
|
display_names = ["Display 1", "Display 2", "Display 3"]
|
||||||
|
current_index = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Add value application logic**:
|
||||||
|
```gdscript
|
||||||
|
func _apply_value_change(new_value: String, index: int):
|
||||||
|
match data_source:
|
||||||
|
"your_custom_type":
|
||||||
|
# Apply your custom logic here
|
||||||
|
YourManager.set_custom_setting(new_value)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Formatting
|
||||||
|
|
||||||
|
Override `_update_display()` for custom display formatting:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
func _update_display():
|
||||||
|
if data_source == "your_custom_type":
|
||||||
|
# Custom formatting logic
|
||||||
|
value_display.text = "Custom: " + display_names[current_index]
|
||||||
|
else:
|
||||||
|
super._update_display() # Call parent implementation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### When to Use ValueStepper
|
||||||
|
- ✅ **Discrete options**: Language, difficulty, resolution, theme
|
||||||
|
- ✅ **Settings menus**: Any option with predefined choices
|
||||||
|
- ✅ **Game configuration**: Graphics quality, control schemes
|
||||||
|
- ✅ **Limited options**: 2-10 options work best
|
||||||
|
|
||||||
|
### When NOT to Use ValueStepper
|
||||||
|
- ❌ **Continuous values**: Use sliders for volume, brightness
|
||||||
|
- ❌ **Large lists**: Use ItemList or OptionButton for 20+ items
|
||||||
|
- ❌ **Text input**: Use LineEdit for user-entered text
|
||||||
|
- ❌ **Numeric input**: Use SpinBox for number entry
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
- ValueStepper is lightweight and suitable for multiple instances
|
||||||
|
- Data loading happens once in `_ready()`
|
||||||
|
- Visual updates are minimal (just text changes)
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
- Visual highlighting provides clear focus indication
|
||||||
|
- Audio feedback confirms user actions
|
||||||
|
- Keyboard and gamepad support for non-mouse users
|
||||||
|
- Consistent navigation patterns
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
### Stepper Not Responding to Input
|
||||||
|
- Ensure `handle_input_action()` is called from parent's `_input()`
|
||||||
|
- Check that the stepper has proper focus/highlighting
|
||||||
|
- Verify input actions are defined in project input map
|
||||||
|
|
||||||
|
### Values Not Saving
|
||||||
|
- Override `_apply_value_change()` to handle persistence
|
||||||
|
- Connect to `value_changed` signal for custom save logic
|
||||||
|
- Ensure SettingsManager or your data manager is configured
|
||||||
|
|
||||||
|
### Display Names Not Showing
|
||||||
|
- Check that `display_names` array is properly populated
|
||||||
|
- Ensure `display_names.size()` matches `values.size()`
|
||||||
|
- Verify `_update_display()` is called after data loading
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
scenes/ui/components/
|
||||||
|
├── ValueStepper.gd # Main component script
|
||||||
|
└── ValueStepper.tscn # Component scene
|
||||||
|
|
||||||
|
examples/
|
||||||
|
├── ValueStepperExample.gd # Usage example script
|
||||||
|
└── ValueStepperExample.tscn # Example scene
|
||||||
|
|
||||||
|
docs/
|
||||||
|
└── UI_COMPONENTS.md # This documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
This component provides a solid foundation for any game's settings system and can be easily extended for project-specific needs.
|
||||||
106
examples/ValueStepperExample.gd
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Example of how to use the ValueStepper component in any scene
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
# Example of setting up custom navigation
|
||||||
|
var navigable_steppers: Array[ValueStepper] = []
|
||||||
|
var current_stepper_index: int = 0
|
||||||
|
|
||||||
|
@onready
|
||||||
|
var language_stepper: ValueStepper = $VBoxContainer/Examples/LanguageContainer/LanguageStepper
|
||||||
|
@onready
|
||||||
|
var difficulty_stepper: ValueStepper = $VBoxContainer/Examples/DifficultyContainer/DifficultyStepper
|
||||||
|
@onready
|
||||||
|
var resolution_stepper: ValueStepper = $VBoxContainer/Examples/ResolutionContainer/ResolutionStepper
|
||||||
|
@onready
|
||||||
|
var custom_stepper: ValueStepper = $VBoxContainer/Examples/CustomContainer/CustomStepper
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
DebugManager.log_info("ValueStepper example ready", "Example")
|
||||||
|
|
||||||
|
# Setup navigation array
|
||||||
|
navigable_steppers = [
|
||||||
|
language_stepper, difficulty_stepper, resolution_stepper, custom_stepper
|
||||||
|
]
|
||||||
|
|
||||||
|
# Connect to value change events
|
||||||
|
for stepper in navigable_steppers:
|
||||||
|
if not stepper.value_changed.is_connected(_on_stepper_value_changed):
|
||||||
|
stepper.value_changed.connect(_on_stepper_value_changed)
|
||||||
|
|
||||||
|
# Setup custom stepper with custom values
|
||||||
|
var themes = ["Light", "Dark", "Blue", "Green", "Purple"]
|
||||||
|
var theme_values = ["light", "dark", "blue", "green", "purple"]
|
||||||
|
custom_stepper.setup_custom_values(theme_values, themes)
|
||||||
|
custom_stepper.data_source = "theme" # For better logging
|
||||||
|
|
||||||
|
# Highlight first stepper
|
||||||
|
_update_stepper_highlighting()
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent):
|
||||||
|
# Example navigation handling
|
||||||
|
if event.is_action_pressed("move_up"):
|
||||||
|
_navigate_steppers(-1)
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event.is_action_pressed("move_down"):
|
||||||
|
_navigate_steppers(1)
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event.is_action_pressed("move_left"):
|
||||||
|
_handle_stepper_input("move_left")
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event.is_action_pressed("move_right"):
|
||||||
|
_handle_stepper_input("move_right")
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
func _navigate_steppers(direction: int):
|
||||||
|
current_stepper_index = (
|
||||||
|
(current_stepper_index + direction) % navigable_steppers.size()
|
||||||
|
)
|
||||||
|
if current_stepper_index < 0:
|
||||||
|
current_stepper_index = navigable_steppers.size() - 1
|
||||||
|
_update_stepper_highlighting()
|
||||||
|
DebugManager.log_info(
|
||||||
|
"Stepper navigation: index " + str(current_stepper_index), "Example"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _handle_stepper_input(action: String):
|
||||||
|
if (
|
||||||
|
current_stepper_index >= 0
|
||||||
|
and current_stepper_index < navigable_steppers.size()
|
||||||
|
):
|
||||||
|
var stepper = navigable_steppers[current_stepper_index]
|
||||||
|
if stepper.handle_input_action(action):
|
||||||
|
AudioManager.play_ui_click()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_stepper_highlighting():
|
||||||
|
for i in range(navigable_steppers.size()):
|
||||||
|
navigable_steppers[i].set_highlighted(i == current_stepper_index)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_stepper_value_changed(new_value: String, new_index: int):
|
||||||
|
DebugManager.log_info(
|
||||||
|
(
|
||||||
|
"Stepper value changed to: "
|
||||||
|
+ new_value
|
||||||
|
+ " (index: "
|
||||||
|
+ str(new_index)
|
||||||
|
+ ")"
|
||||||
|
),
|
||||||
|
"Example"
|
||||||
|
)
|
||||||
|
# Handle value change in your scene
|
||||||
|
# For example: apply settings, save preferences, update UI, etc.
|
||||||
|
|
||||||
|
|
||||||
|
# Example of programmatically setting values
|
||||||
|
func _on_reset_to_defaults_pressed():
|
||||||
|
AudioManager.play_ui_click()
|
||||||
|
language_stepper.set_current_value("en")
|
||||||
|
difficulty_stepper.set_current_value("normal")
|
||||||
|
resolution_stepper.set_current_value("1920x1080")
|
||||||
|
custom_stepper.set_current_value("dark")
|
||||||
|
DebugManager.log_info("Reset all steppers to defaults", "Example")
|
||||||
1
examples/ValueStepperExample.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bu6f2vbdku4gg
|
||||||
113
examples/ValueStepperExample.tscn
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://cw03putw85q1o"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://examples/ValueStepperExample.gd" id="1_example"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/ui/components/ValueStepper.tscn" id="2_value_stepper"]
|
||||||
|
|
||||||
|
[node name="ValueStepperExample" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_example")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -200.0
|
||||||
|
offset_top = -150.0
|
||||||
|
offset_right = 200.0
|
||||||
|
offset_bottom = 150.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "ValueStepper Component Examples"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Examples" type="VBoxContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="LanguageContainer" type="HBoxContainer" parent="VBoxContainer/Examples"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="LanguageLabel" type="Label" parent="VBoxContainer/Examples/LanguageContainer"]
|
||||||
|
custom_minimum_size = Vector2(120, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Language:"
|
||||||
|
|
||||||
|
[node name="LanguageStepper" parent="VBoxContainer/Examples/LanguageContainer" instance=ExtResource("2_value_stepper")]
|
||||||
|
layout_mode = 2
|
||||||
|
data_source = "language"
|
||||||
|
|
||||||
|
[node name="DifficultyContainer" type="HBoxContainer" parent="VBoxContainer/Examples"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="DifficultyLabel" type="Label" parent="VBoxContainer/Examples/DifficultyContainer"]
|
||||||
|
custom_minimum_size = Vector2(120, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Difficulty:"
|
||||||
|
|
||||||
|
[node name="DifficultyStepper" parent="VBoxContainer/Examples/DifficultyContainer" instance=ExtResource("2_value_stepper")]
|
||||||
|
layout_mode = 2
|
||||||
|
data_source = "difficulty"
|
||||||
|
|
||||||
|
[node name="ResolutionContainer" type="HBoxContainer" parent="VBoxContainer/Examples"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ResolutionLabel" type="Label" parent="VBoxContainer/Examples/ResolutionContainer"]
|
||||||
|
custom_minimum_size = Vector2(120, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Resolution:"
|
||||||
|
|
||||||
|
[node name="ResolutionStepper" parent="VBoxContainer/Examples/ResolutionContainer" instance=ExtResource("2_value_stepper")]
|
||||||
|
layout_mode = 2
|
||||||
|
data_source = "resolution"
|
||||||
|
|
||||||
|
[node name="CustomContainer" type="HBoxContainer" parent="VBoxContainer/Examples"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="CustomLabel" type="Label" parent="VBoxContainer/Examples/CustomContainer"]
|
||||||
|
custom_minimum_size = Vector2(120, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Theme:"
|
||||||
|
|
||||||
|
[node name="CustomStepper" parent="VBoxContainer/Examples/CustomContainer" instance=ExtResource("2_value_stepper")]
|
||||||
|
layout_mode = 2
|
||||||
|
data_source = "custom"
|
||||||
|
|
||||||
|
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Instructions" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Navigation:
|
||||||
|
• Up/Down arrows - Navigate between steppers
|
||||||
|
• Left/Right arrows - Change values
|
||||||
|
• Mouse clicks work on arrow buttons
|
||||||
|
|
||||||
|
Features:
|
||||||
|
• Multiple data sources (language, difficulty, resolution)
|
||||||
|
• Custom values support (theme example)
|
||||||
|
• Gamepad/keyboard navigation
|
||||||
|
• Visual highlighting
|
||||||
|
• Signal-based value changes"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="HSeparator3" type="HSeparator" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ResetButton" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Reset to Defaults"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="VBoxContainer/ResetButton" to="." method="_on_reset_to_defaults_pressed"]
|
||||||
394
export_presets.cfg
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
[preset.0]
|
||||||
|
|
||||||
|
name="Windows Desktop"
|
||||||
|
platform="Windows Desktop"
|
||||||
|
runnable=true
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
export_files=PackedStringArray()
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="builds/skelly-windows.exe"
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
|
||||||
|
[preset.0.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_wrapper=1
|
||||||
|
binary_format/embed_pck=true
|
||||||
|
texture_format/bptc=true
|
||||||
|
texture_format/s3tc=true
|
||||||
|
texture_format/etc=false
|
||||||
|
texture_format/etc2=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
codesign/enable=false
|
||||||
|
codesign/identity=""
|
||||||
|
codesign/password=""
|
||||||
|
codesign/timestamp=true
|
||||||
|
codesign/timestamp_server_url=""
|
||||||
|
codesign/digest_algorithm=1
|
||||||
|
codesign/description=""
|
||||||
|
codesign/custom_options=PackedStringArray()
|
||||||
|
application/modify_resources=true
|
||||||
|
application/icon=""
|
||||||
|
application/console_wrapper_icon=""
|
||||||
|
application/icon_interpolation=4
|
||||||
|
application/file_version=""
|
||||||
|
application/product_version=""
|
||||||
|
application/company_name=""
|
||||||
|
application/product_name="Skelly"
|
||||||
|
application/file_description=""
|
||||||
|
application/copyright=""
|
||||||
|
application/trademarks=""
|
||||||
|
application/export_angle=0
|
||||||
|
ssh_remote_deploy/enabled=false
|
||||||
|
ssh_remote_deploy/host="user@host_ip"
|
||||||
|
ssh_remote_deploy/port="22"
|
||||||
|
ssh_remote_deploy/extra_args_ssh=""
|
||||||
|
ssh_remote_deploy/extra_args_scp=""
|
||||||
|
ssh_remote_deploy/run_script=""
|
||||||
|
ssh_remote_deploy/cleanup_script=""
|
||||||
|
|
||||||
|
[preset.1]
|
||||||
|
|
||||||
|
name="Linux"
|
||||||
|
platform="Linux/X11"
|
||||||
|
runnable=true
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
export_files=PackedStringArray()
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="builds/skelly-linux.x86_64"
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
|
||||||
|
[preset.1.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_wrapper=1
|
||||||
|
binary_format/embed_pck=true
|
||||||
|
texture_format/bptc=true
|
||||||
|
texture_format/s3tc=true
|
||||||
|
texture_format/etc=false
|
||||||
|
texture_format/etc2=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
ssh_remote_deploy/enabled=false
|
||||||
|
ssh_remote_deploy/host="user@host_ip"
|
||||||
|
ssh_remote_deploy/port="22"
|
||||||
|
ssh_remote_deploy/extra_args_ssh=""
|
||||||
|
ssh_remote_deploy/extra_args_scp=""
|
||||||
|
ssh_remote_deploy/run_script=""
|
||||||
|
ssh_remote_deploy/cleanup_script=""
|
||||||
|
|
||||||
|
[preset.2]
|
||||||
|
|
||||||
|
name="macOS"
|
||||||
|
platform="macOS"
|
||||||
|
runnable=true
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
export_files=PackedStringArray()
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="builds/skelly-macos.zip"
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
|
||||||
|
[preset.2.options]
|
||||||
|
|
||||||
|
binary_format/architecture="universal"
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_wrapper=1
|
||||||
|
application/icon=""
|
||||||
|
application/icon_interpolation=4
|
||||||
|
application/bundle_identifier="com.skelly.game"
|
||||||
|
application/signature=""
|
||||||
|
application/app_category="Games"
|
||||||
|
application/short_version="1.0"
|
||||||
|
application/version="1.0"
|
||||||
|
application/copyright=""
|
||||||
|
application/copyright_localized={}
|
||||||
|
application/min_macos_version="10.12"
|
||||||
|
display/high_res=false
|
||||||
|
xcode/platform_build="14C18"
|
||||||
|
xcode/sdk_version="13.1"
|
||||||
|
xcode/sdk_name="macosx13.1"
|
||||||
|
xcode/sdk_build="22C55"
|
||||||
|
xcode/xcode_version="1420"
|
||||||
|
xcode/xcode_build="14C18"
|
||||||
|
codesign/codesign=1
|
||||||
|
codesign/installer_identity=""
|
||||||
|
codesign/apple_team_id=""
|
||||||
|
codesign/identity=""
|
||||||
|
codesign/entitlements/custom_file=""
|
||||||
|
codesign/entitlements/allow_jit_code_execution=false
|
||||||
|
codesign/entitlements/allow_unsigned_executable_memory=false
|
||||||
|
codesign/entitlements/allow_dyld_environment_variables=false
|
||||||
|
codesign/entitlements/disable_library_validation=false
|
||||||
|
codesign/entitlements/audio_input=false
|
||||||
|
codesign/entitlements/camera=false
|
||||||
|
codesign/entitlements/location=false
|
||||||
|
codesign/entitlements/address_book=false
|
||||||
|
codesign/entitlements/calendars=false
|
||||||
|
codesign/entitlements/photos_library=false
|
||||||
|
codesign/entitlements/apple_events=false
|
||||||
|
codesign/entitlements/debugging=false
|
||||||
|
codesign/entitlements/app_sandbox/enabled=false
|
||||||
|
codesign/entitlements/app_sandbox/network_server=false
|
||||||
|
codesign/entitlements/app_sandbox/network_client=false
|
||||||
|
codesign/entitlements/app_sandbox/device_usb=false
|
||||||
|
codesign/entitlements/app_sandbox/device_bluetooth=false
|
||||||
|
codesign/entitlements/app_sandbox/files_downloads=0
|
||||||
|
codesign/entitlements/app_sandbox/files_pictures=0
|
||||||
|
codesign/entitlements/app_sandbox/files_music=0
|
||||||
|
codesign/entitlements/app_sandbox/files_movies=0
|
||||||
|
codesign/entitlements/app_sandbox/helper_executables=[]
|
||||||
|
notarization/notarization=0
|
||||||
|
privacy/microphone_usage_description=""
|
||||||
|
privacy/microphone_usage_description_localized={}
|
||||||
|
privacy/camera_usage_description=""
|
||||||
|
privacy/camera_usage_description_localized={}
|
||||||
|
privacy/location_usage_description=""
|
||||||
|
privacy/location_usage_description_localized={}
|
||||||
|
privacy/address_book_usage_description=""
|
||||||
|
privacy/address_book_usage_description_localized={}
|
||||||
|
privacy/calendar_usage_description=""
|
||||||
|
privacy/calendar_usage_description_localized={}
|
||||||
|
privacy/photos_library_usage_description=""
|
||||||
|
privacy/photos_library_usage_description_localized={}
|
||||||
|
privacy/desktop_folder_usage_description=""
|
||||||
|
privacy/desktop_folder_usage_description_localized={}
|
||||||
|
privacy/documents_folder_usage_description=""
|
||||||
|
privacy/documents_folder_usage_description_localized={}
|
||||||
|
privacy/downloads_folder_usage_description=""
|
||||||
|
privacy/downloads_folder_usage_description_localized={}
|
||||||
|
privacy/network_volumes_usage_description=""
|
||||||
|
privacy/network_volumes_usage_description_localized={}
|
||||||
|
privacy/removable_volumes_usage_description=""
|
||||||
|
privacy/removable_volumes_usage_description_localized={}
|
||||||
|
ssh_remote_deploy/enabled=false
|
||||||
|
ssh_remote_deploy/host="user@host_ip"
|
||||||
|
ssh_remote_deploy/port="22"
|
||||||
|
ssh_remote_deploy/extra_args_ssh=""
|
||||||
|
ssh_remote_deploy/extra_args_scp=""
|
||||||
|
ssh_remote_deploy/run_script=""
|
||||||
|
ssh_remote_deploy/cleanup_script=""
|
||||||
|
|
||||||
|
[preset.3]
|
||||||
|
|
||||||
|
name="Android"
|
||||||
|
platform="Android"
|
||||||
|
runnable=true
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
export_files=PackedStringArray()
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="builds/skelly-android.apk"
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
|
||||||
|
[preset.3.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
gradle_build/use_gradle_build=false
|
||||||
|
gradle_build/export_format=0
|
||||||
|
gradle_build/min_sdk=""
|
||||||
|
gradle_build/target_sdk=""
|
||||||
|
architectures/armeabi-v7a=false
|
||||||
|
architectures/arm64-v8a=true
|
||||||
|
architectures/x86=false
|
||||||
|
architectures/x86_64=false
|
||||||
|
version/code=1
|
||||||
|
version/name="1.0"
|
||||||
|
package/unique_name="com.skelly.game"
|
||||||
|
package/name="Skelly"
|
||||||
|
package/signed=true
|
||||||
|
package/app_category=2
|
||||||
|
package/retain_data_on_uninstall=false
|
||||||
|
package/exclude_from_recents=false
|
||||||
|
launcher_icons/main_192x192=""
|
||||||
|
launcher_icons/adaptive_foreground_432x432=""
|
||||||
|
launcher_icons/adaptive_background_432x432=""
|
||||||
|
graphics/32_bits_framebuffer=true
|
||||||
|
graphics/opengl_debug=false
|
||||||
|
xr_features/xr_mode=0
|
||||||
|
xr_features/hand_tracking=0
|
||||||
|
xr_features/hand_tracking_frequency=0
|
||||||
|
xr_features/passthrough=0
|
||||||
|
screen/immersive_mode=true
|
||||||
|
screen/orientation=0
|
||||||
|
screen/support_small=true
|
||||||
|
screen/support_normal=true
|
||||||
|
screen/support_large=true
|
||||||
|
screen/support_xlarge=true
|
||||||
|
user_data_backup/allow=false
|
||||||
|
command_line/extra_args=""
|
||||||
|
apk_expansion/enable=false
|
||||||
|
apk_expansion/SALT=""
|
||||||
|
apk_expansion/public_key=""
|
||||||
|
permissions/custom_permissions=PackedStringArray()
|
||||||
|
permissions/access_checkin_properties=false
|
||||||
|
permissions/access_coarse_location=false
|
||||||
|
permissions/access_fine_location=false
|
||||||
|
permissions/access_location_extra_commands=false
|
||||||
|
permissions/access_mock_location=false
|
||||||
|
permissions/access_network_state=false
|
||||||
|
permissions/access_surface_flinger=false
|
||||||
|
permissions/access_wifi_state=false
|
||||||
|
permissions/account_manager=false
|
||||||
|
permissions/add_voicemail=false
|
||||||
|
permissions/authenticate_accounts=false
|
||||||
|
permissions/battery_stats=false
|
||||||
|
permissions/bind_accessibility_service=false
|
||||||
|
permissions/bind_appwidget=false
|
||||||
|
permissions/bind_device_admin=false
|
||||||
|
permissions/bind_input_method=false
|
||||||
|
permissions/bind_nfc_service=false
|
||||||
|
permissions/bind_notification_listener_service=false
|
||||||
|
permissions/bind_print_service=false
|
||||||
|
permissions/bind_remoteviews=false
|
||||||
|
permissions/bind_text_service=false
|
||||||
|
permissions/bind_vpn_service=false
|
||||||
|
permissions/bind_wallpaper=false
|
||||||
|
permissions/bluetooth=false
|
||||||
|
permissions/bluetooth_admin=false
|
||||||
|
permissions/bluetooth_privileged=false
|
||||||
|
permissions/brick=false
|
||||||
|
permissions/broadcast_package_removed=false
|
||||||
|
permissions/broadcast_sms=false
|
||||||
|
permissions/broadcast_sticky=false
|
||||||
|
permissions/broadcast_wap_push=false
|
||||||
|
permissions/call_phone=false
|
||||||
|
permissions/call_privileged=false
|
||||||
|
permissions/camera=false
|
||||||
|
permissions/capture_audio_output=false
|
||||||
|
permissions/capture_secure_video_output=false
|
||||||
|
permissions/capture_video_output=false
|
||||||
|
permissions/change_component_enabled_state=false
|
||||||
|
permissions/change_configuration=false
|
||||||
|
permissions/change_network_state=false
|
||||||
|
permissions/change_wifi_multicast_state=false
|
||||||
|
permissions/change_wifi_state=false
|
||||||
|
permissions/clear_app_cache=false
|
||||||
|
permissions/clear_app_user_data=false
|
||||||
|
permissions/control_location_updates=false
|
||||||
|
permissions/delete_cache_files=false
|
||||||
|
permissions/delete_packages=false
|
||||||
|
permissions/device_power=false
|
||||||
|
permissions/diagnostic=false
|
||||||
|
permissions/disable_keyguard=false
|
||||||
|
permissions/dump=false
|
||||||
|
permissions/expand_status_bar=false
|
||||||
|
permissions/factory_test=false
|
||||||
|
permissions/flashlight=false
|
||||||
|
permissions/force_back=false
|
||||||
|
permissions/get_accounts=false
|
||||||
|
permissions/get_package_size=false
|
||||||
|
permissions/get_tasks=false
|
||||||
|
permissions/get_top_activity_info=false
|
||||||
|
permissions/global_search=false
|
||||||
|
permissions/hardware_test=false
|
||||||
|
permissions/inject_events=false
|
||||||
|
permissions/install_location_provider=false
|
||||||
|
permissions/install_packages=false
|
||||||
|
permissions/install_shortcut=false
|
||||||
|
permissions/internal_system_window=false
|
||||||
|
permissions/internet=false
|
||||||
|
permissions/kill_background_processes=false
|
||||||
|
permissions/location_hardware=false
|
||||||
|
permissions/manage_accounts=false
|
||||||
|
permissions/manage_app_tokens=false
|
||||||
|
permissions/manage_documents=false
|
||||||
|
permissions/manage_external_storage=false
|
||||||
|
permissions/master_clear=false
|
||||||
|
permissions/media_content_control=false
|
||||||
|
permissions/modify_audio_settings=false
|
||||||
|
permissions/modify_phone_state=false
|
||||||
|
permissions/mount_format_filesystems=false
|
||||||
|
permissions/mount_unmount_filesystems=false
|
||||||
|
permissions/nfc=false
|
||||||
|
permissions/persistent_activity=false
|
||||||
|
permissions/process_outgoing_calls=false
|
||||||
|
permissions/read_calendar=false
|
||||||
|
permissions/read_call_log=false
|
||||||
|
permissions/read_contacts=false
|
||||||
|
permissions/read_external_storage=false
|
||||||
|
permissions/read_frame_buffer=false
|
||||||
|
permissions/read_history_bookmarks=false
|
||||||
|
permissions/read_input_state=false
|
||||||
|
permissions/read_logs=false
|
||||||
|
permissions/read_phone_state=false
|
||||||
|
permissions/read_profile=false
|
||||||
|
permissions/read_sms=false
|
||||||
|
permissions/read_social_stream=false
|
||||||
|
permissions/read_sync_settings=false
|
||||||
|
permissions/read_sync_stats=false
|
||||||
|
permissions/read_user_dictionary=false
|
||||||
|
permissions/reboot=false
|
||||||
|
permissions/receive_boot_completed=false
|
||||||
|
permissions/receive_mms=false
|
||||||
|
permissions/receive_sms=false
|
||||||
|
permissions/receive_wap_push=false
|
||||||
|
permissions/record_audio=false
|
||||||
|
permissions/reorder_tasks=false
|
||||||
|
permissions/restart_packages=false
|
||||||
|
permissions/send_respond_via_message=false
|
||||||
|
permissions/send_sms=false
|
||||||
|
permissions/set_activity_watcher=false
|
||||||
|
permissions/set_alarm=false
|
||||||
|
permissions/set_always_finish=false
|
||||||
|
permissions/set_animation_scale=false
|
||||||
|
permissions/set_debug_app=false
|
||||||
|
permissions/set_orientation=false
|
||||||
|
permissions/set_pointer_speed=false
|
||||||
|
permissions/set_preferred_applications=false
|
||||||
|
permissions/set_process_limit=false
|
||||||
|
permissions/set_time=false
|
||||||
|
permissions/set_time_zone=false
|
||||||
|
permissions/set_wallpaper=false
|
||||||
|
permissions/set_wallpaper_hints=false
|
||||||
|
permissions/signal_persistent_processes=false
|
||||||
|
permissions/status_bar=false
|
||||||
|
permissions/subscribed_feeds_read=false
|
||||||
|
permissions/subscribed_feeds_write=false
|
||||||
|
permissions/system_alert_window=false
|
||||||
|
permissions/transmit_ir=false
|
||||||
|
permissions/uninstall_shortcut=false
|
||||||
|
permissions/update_device_stats=false
|
||||||
|
permissions/use_credentials=false
|
||||||
|
permissions/use_sip=false
|
||||||
|
permissions/vibrate=false
|
||||||
|
permissions/wake_lock=false
|
||||||
|
permissions/write_apn_settings=false
|
||||||
|
permissions/write_calendar=false
|
||||||
|
permissions/write_call_log=false
|
||||||
|
permissions/write_contacts=false
|
||||||
|
permissions/write_external_storage=false
|
||||||
|
permissions/write_gservices=false
|
||||||
|
permissions/write_history_bookmarks=false
|
||||||
|
permissions/write_profile=false
|
||||||
|
permissions/write_secure_settings=false
|
||||||
|
permissions/write_settings=false
|
||||||
|
permissions/write_sms=false
|
||||||
|
permissions/write_social_stream=false
|
||||||
|
permissions/write_sync_settings=false
|
||||||
|
permissions/write_user_dictionary=false
|
||||||
46
gdlintrc
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
class-definitions-order:
|
||||||
|
- tools
|
||||||
|
- classnames
|
||||||
|
- extends
|
||||||
|
- signals
|
||||||
|
- enums
|
||||||
|
- consts
|
||||||
|
- exports
|
||||||
|
- pubvars
|
||||||
|
- prvvars
|
||||||
|
- onreadypubvars
|
||||||
|
- onreadyprvvars
|
||||||
|
- others
|
||||||
|
class-load-variable-name: (([A-Z][a-z0-9]*)+|_?[a-z][a-z0-9]*(_[a-z0-9]+)*)
|
||||||
|
class-name: ([A-Z][a-z0-9]*)+
|
||||||
|
class-variable-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||||
|
comparison-with-itself: null
|
||||||
|
constant-name: '[A-Z][A-Z0-9]*(_[A-Z0-9]+)*'
|
||||||
|
disable: []
|
||||||
|
duplicated-load: null
|
||||||
|
enum-element-name: '[A-Z][A-Z0-9]*(_[A-Z0-9]+)*'
|
||||||
|
enum-name: ([A-Z][a-z0-9]*)+
|
||||||
|
excluded_directories: !!set
|
||||||
|
.git: null
|
||||||
|
expression-not-assigned: null
|
||||||
|
function-argument-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||||
|
function-arguments-number: 10
|
||||||
|
function-name: (_on_([A-Z][a-z0-9]*)+(_[a-z0-9]+)*|_?[a-z][a-z0-9]*(_[a-z0-9]+)*)
|
||||||
|
function-preload-variable-name: ([A-Z][a-z0-9]*)+
|
||||||
|
function-variable-name: '[a-z][a-z0-9]*(_[a-z0-9]+)*'
|
||||||
|
load-constant-name: (([A-Z][a-z0-9]*)+|[A-Z][A-Z0-9]*(_[A-Z0-9]+)*)
|
||||||
|
loop-variable-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||||
|
max-file-lines: 1500
|
||||||
|
max-line-length: 120
|
||||||
|
max-public-methods: 20
|
||||||
|
max-returns: 6
|
||||||
|
mixed-tabs-and-spaces: null
|
||||||
|
no-elif-return: null
|
||||||
|
no-else-return: null
|
||||||
|
private-method-call: null
|
||||||
|
signal-name: '[a-z][a-z0-9]*(_[a-z0-9]+)*'
|
||||||
|
sub-class-name: _?([A-Z][a-z0-9]*)+
|
||||||
|
tab-characters: 1
|
||||||
|
trailing-whitespace: null
|
||||||
|
unnecessary-pass: null
|
||||||
|
unused-argument: null
|
||||||
@@ -6,3 +6,13 @@ music_volume;"Music Volume";"Громкость музыки"
|
|||||||
sfx_volume;"SFX Volume";"Громкость эффектов"
|
sfx_volume;"SFX Volume";"Громкость эффектов"
|
||||||
language;"Language";"Язык"
|
language;"Language";"Язык"
|
||||||
back;"Back";"Назад"
|
back;"Back";"Назад"
|
||||||
|
reset_progress;"Reset All Progress";"Сбросить весь прогресс"
|
||||||
|
confirm_reset_title;"Confirm Progress Reset";"Подтвердите сброс прогресса"
|
||||||
|
confirm_reset_message;"Are you sure you want to delete ALL your progress?\n\nThis will permanently remove:\n• All scores and high scores\n• Game statistics\n• Saved game states\n\nThis action cannot be undone!";"Вы уверены, что хотите удалить ВЕСЬ свой прогресс?\n\nЭто навсегда удалит:\n• Все очки и рекорды\n• Игровую статистику\n• Сохранённые игровые состояния\n\nЭто действие нельзя отменить!"
|
||||||
|
reset_confirm;"Yes, Reset Everything";"Да, сбросить всё"
|
||||||
|
cancel;"Cancel";"Отмена"
|
||||||
|
reset_success_title;"Progress Reset";"Прогресс сброшен"
|
||||||
|
reset_success_message;"All progress has been successfully deleted.\nYour game has been reset to the beginning.";"Весь прогресс был успешно удалён.\nВаша игра была сброшена к началу."
|
||||||
|
reset_error_title;"Reset Failed";"Ошибка сброса"
|
||||||
|
reset_error_message;"There was an error resetting your progress.\nPlease try again.";"Произошла ошибка при сбросе вашего прогресса.\nПопробуйте ещё раз."
|
||||||
|
ok;"OK";"ОК"
|
||||||
|
|||||||
|
187
project.godot
@@ -11,16 +11,32 @@ config_version=5
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Skelly"
|
config/name="Skelly"
|
||||||
run/main_scene="uid://ci2gk11211n0d"
|
run/main_scene="res://scenes/main/Main.tscn"
|
||||||
config/features=PackedStringArray("4.4", "Mobile")
|
config/features=PackedStringArray("4.4", "Mobile")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
boot_splash/handheld/orientation=0
|
||||||
|
boot_splash/stretch/aspect="keep"
|
||||||
|
|
||||||
|
[audio]
|
||||||
|
|
||||||
|
buses/default_bus_layout="res://data/default_bus_layout.tres"
|
||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
SettingsManager="*res://scripts/SettingsManager.gd"
|
SettingsManager="*res://src/autoloads/SettingsManager.gd"
|
||||||
AudioManager="*res://scripts/AudioManager.gd"
|
AudioManager="*res://src/autoloads/AudioManager.gd"
|
||||||
GameManager="*res://scripts/GameManager.gd"
|
GameManager="*res://src/autoloads/GameManager.gd"
|
||||||
LocalizationManager="*res://scripts/LocalizationManager.gd"
|
LocalizationManager="*res://src/autoloads/LocalizationManager.gd"
|
||||||
|
DebugManager="*res://src/autoloads/DebugManager.gd"
|
||||||
|
SaveManager="*res://src/autoloads/SaveManager.gd"
|
||||||
|
UIConstants="*res://src/autoloads/UIConstants.gd"
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/size/viewport_width=1024
|
||||||
|
window/size/viewport_height=768
|
||||||
|
window/stretch/mode="canvas_items"
|
||||||
|
window/handheld/orientation=4
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
||||||
@@ -30,9 +46,10 @@ ui_pause={
|
|||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
any_key={
|
action_confirm={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
||||||
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(165, 16),"global_position":Vector2(174, 64),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(165, 16),"global_position":Vector2(174, 64),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||||
]
|
]
|
||||||
@@ -43,6 +60,160 @@ ui_menu_toggle={
|
|||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
action_south={
|
||||||
|
"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":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
action_east={
|
||||||
|
"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":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":4194308,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
action_west={
|
||||||
|
"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(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":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":72,"key_label":0,"unicode":104,"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_north={
|
||||||
|
"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":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
move_up={
|
||||||
|
"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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
move_down={
|
||||||
|
"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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
move_left={
|
||||||
|
"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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":72,"key_label":0,"unicode":104,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
move_right={
|
||||||
|
"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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
shoulder_left={
|
||||||
|
"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":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":90,"key_label":0,"unicode":122,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
shoulder_right={
|
||||||
|
"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":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":5,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
trigger_left={
|
||||||
|
"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":4194323,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":91,"key_label":0,"unicode":91,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
trigger_right={
|
||||||
|
"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":4194324,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":93,"key_label":0,"unicode":93,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":7,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
pause_menu={
|
||||||
|
"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":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
options_menu={
|
||||||
|
"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":79,"key_label":0,"unicode":111,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
special_1={
|
||||||
|
"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":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":8,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
special_2={
|
||||||
|
"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":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
restart_level={
|
||||||
|
"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":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
next_level={
|
||||||
|
"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":78,"key_label":0,"unicode":110,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
toggle_music={
|
||||||
|
"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":77,"key_label":0,"unicode":109,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
toggle_sound={
|
||||||
|
"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":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, 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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fullscreen={
|
||||||
|
"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":4194342,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
quit_game={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194335,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ui_back={
|
||||||
|
"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":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
[internationalization]
|
[internationalization]
|
||||||
|
|
||||||
@@ -51,4 +222,6 @@ locale/translations=PackedStringArray("res://localization/MainStrings.en.transla
|
|||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
textures/canvas_textures/default_texture_filter=0
|
textures/canvas_textures/default_texture_filter=0
|
||||||
renderer/rendering_method="mobile"
|
renderer/rendering_method="gl_compatibility"
|
||||||
|
renderer/rendering_method.mobile="gl_compatibility"
|
||||||
|
textures/vram_compression/import_etc2_astc=true
|
||||||
|
|||||||
10
requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
aiofiles>=23
|
||||||
|
gdtoolkit>=4
|
||||||
|
PyYAML>=6.0
|
||||||
|
ruff>=0.1.0
|
||||||
|
setuptools<81
|
||||||
|
yamllint>=1
|
||||||
|
|
||||||
|
# Diagram generation and rendering
|
||||||
|
matplotlib>=3.8.0 # Chart and graph generation
|
||||||
|
pillow>=10.0.0 # Image processing and manipulation
|
||||||
232
run_dev.bat
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
REM =============================================================================
|
||||||
|
REM Skelly Development Tools Runner
|
||||||
|
REM =============================================================================
|
||||||
|
REM
|
||||||
|
REM This script runs development tools for the Skelly Godot project.
|
||||||
|
REM By default, it runs all checks: linting, formatting, and testing.
|
||||||
|
REM
|
||||||
|
REM Usage:
|
||||||
|
REM run_dev.bat - Run all checks (lint + format + test)
|
||||||
|
REM run_dev.bat --lint - Run only linting
|
||||||
|
REM run_dev.bat --format - Run only formatting
|
||||||
|
REM run_dev.bat --test - Run only tests
|
||||||
|
REM run_dev.bat --help - Show this help message
|
||||||
|
REM run_dev.bat --steps lint test - Run specific steps in order
|
||||||
|
REM
|
||||||
|
REM =============================================================================
|
||||||
|
|
||||||
|
REM Initialize variables
|
||||||
|
set "ARG_LINT_ONLY="
|
||||||
|
set "ARG_FORMAT_ONLY="
|
||||||
|
set "ARG_TEST_ONLY="
|
||||||
|
set "ARG_HELP="
|
||||||
|
set "ARG_STEPS="
|
||||||
|
set "CUSTOM_STEPS="
|
||||||
|
|
||||||
|
REM Parse command line arguments
|
||||||
|
:parse_args
|
||||||
|
if "%~1"=="" goto :args_parsed
|
||||||
|
if /i "%~1"=="--lint" (
|
||||||
|
set "ARG_LINT_ONLY=1"
|
||||||
|
shift
|
||||||
|
goto :parse_args
|
||||||
|
)
|
||||||
|
if /i "%~1"=="--format" (
|
||||||
|
set "ARG_FORMAT_ONLY=1"
|
||||||
|
shift
|
||||||
|
goto :parse_args
|
||||||
|
)
|
||||||
|
if /i "%~1"=="--test" (
|
||||||
|
set "ARG_TEST_ONLY=1"
|
||||||
|
shift
|
||||||
|
goto :parse_args
|
||||||
|
)
|
||||||
|
if /i "%~1"=="--help" (
|
||||||
|
set "ARG_HELP=1"
|
||||||
|
shift
|
||||||
|
goto :parse_args
|
||||||
|
)
|
||||||
|
if /i "%~1"=="--steps" (
|
||||||
|
set "ARG_STEPS=1"
|
||||||
|
shift
|
||||||
|
REM Collect remaining arguments as custom steps
|
||||||
|
:collect_steps
|
||||||
|
if "%~1"=="" goto :args_parsed
|
||||||
|
if "!CUSTOM_STEPS!"=="" (
|
||||||
|
set "CUSTOM_STEPS=%~1"
|
||||||
|
) else (
|
||||||
|
set "CUSTOM_STEPS=!CUSTOM_STEPS! %~1"
|
||||||
|
)
|
||||||
|
shift
|
||||||
|
goto :collect_steps
|
||||||
|
)
|
||||||
|
REM Unknown argument
|
||||||
|
echo ❌ Unknown argument: %~1
|
||||||
|
echo Use --help for usage information
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:args_parsed
|
||||||
|
|
||||||
|
REM Show help if requested
|
||||||
|
if defined ARG_HELP (
|
||||||
|
echo.
|
||||||
|
echo 🔧 Skelly Development Tools Runner
|
||||||
|
echo.
|
||||||
|
echo Usage:
|
||||||
|
echo run_dev.bat - Run all checks ^(lint + format + test^)
|
||||||
|
echo run_dev.bat --lint - Run only linting
|
||||||
|
echo run_dev.bat --format - Run only formatting
|
||||||
|
echo run_dev.bat --test - Run only tests
|
||||||
|
echo run_dev.bat --help - Show this help message
|
||||||
|
echo run_dev.bat --steps lint test - Run specific steps in order
|
||||||
|
echo.
|
||||||
|
echo Available steps for --steps:
|
||||||
|
echo lint - Run GDScript linting ^(gdlint^)
|
||||||
|
echo format - Run GDScript formatting ^(gdformat^)
|
||||||
|
echo test - Run Godot tests
|
||||||
|
echo.
|
||||||
|
echo Examples:
|
||||||
|
echo run_dev.bat ^(runs lint, format, test^)
|
||||||
|
echo run_dev.bat --lint ^(runs only linting^)
|
||||||
|
echo run_dev.bat --steps format lint ^(runs format then lint^)
|
||||||
|
echo.
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
|
||||||
|
echo ================================
|
||||||
|
echo 🚀 Development Tools Runner
|
||||||
|
echo ================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Check if Python is available
|
||||||
|
python --version >nul 2>&1
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
echo ❌ ERROR: Python is not installed or not in PATH
|
||||||
|
echo.
|
||||||
|
echo Installation instructions:
|
||||||
|
echo 1. Install Python: winget install Python.Python.3.13
|
||||||
|
echo 2. Restart your command prompt
|
||||||
|
echo 3. Run this script again
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Check if pip is available
|
||||||
|
pip --version >nul 2>&1
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
echo ❌ ERROR: pip is not installed or not in PATH
|
||||||
|
echo Please ensure Python was installed correctly with pip
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Check if Godot is available (only if test step will be run)
|
||||||
|
set "NEED_GODOT="
|
||||||
|
if defined ARG_TEST_ONLY set "NEED_GODOT=1"
|
||||||
|
if defined ARG_STEPS (
|
||||||
|
echo !CUSTOM_STEPS! | findstr /i "test" >nul && set "NEED_GODOT=1"
|
||||||
|
)
|
||||||
|
if not defined ARG_LINT_ONLY if not defined ARG_FORMAT_ONLY if not defined ARG_STEPS set "NEED_GODOT=1"
|
||||||
|
|
||||||
|
if defined NEED_GODOT (
|
||||||
|
godot --version >nul 2>&1
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
echo ❌ ERROR: Godot is not installed or not in PATH
|
||||||
|
echo.
|
||||||
|
echo Installation instructions:
|
||||||
|
echo 1. Download Godot from https://godotengine.org/download
|
||||||
|
echo 2. Add Godot executable to your PATH environment variable
|
||||||
|
echo 3. Or place godot.exe in this project directory
|
||||||
|
echo 4. Restart your command prompt
|
||||||
|
echo 5. Run this script again
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Check if gdlint and gdformat are available (only if needed)
|
||||||
|
set "NEED_GDTOOLS="
|
||||||
|
if defined ARG_LINT_ONLY set "NEED_GDTOOLS=1"
|
||||||
|
if defined ARG_FORMAT_ONLY set "NEED_GDTOOLS=1"
|
||||||
|
if defined ARG_STEPS (
|
||||||
|
echo !CUSTOM_STEPS! | findstr /i /c:"lint" >nul && set "NEED_GDTOOLS=1"
|
||||||
|
echo !CUSTOM_STEPS! | findstr /i /c:"format" >nul && set "NEED_GDTOOLS=1"
|
||||||
|
)
|
||||||
|
if not defined ARG_TEST_ONLY if not defined ARG_STEPS set "NEED_GDTOOLS=1"
|
||||||
|
|
||||||
|
if defined NEED_GDTOOLS (
|
||||||
|
gdlint --version >nul 2>&1
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
echo ❌ ERROR: gdlint is not installed or not in PATH
|
||||||
|
echo.
|
||||||
|
echo Installation instructions:
|
||||||
|
echo 1. pip install --upgrade "setuptools<81"
|
||||||
|
echo 2. pip install gdtoolkit==4
|
||||||
|
echo 3. Restart your command prompt
|
||||||
|
echo 4. Run this script again
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
gdformat --version >nul 2>&1
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
echo ❌ ERROR: gdformat is not installed or not in PATH
|
||||||
|
echo.
|
||||||
|
echo Installation instructions:
|
||||||
|
echo 1. pip install --upgrade "setuptools<81"
|
||||||
|
echo 2. pip install gdtoolkit==4
|
||||||
|
echo 3. Restart your command prompt
|
||||||
|
echo 4. Run this script again
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
echo ✅ All dependencies are available. Running development workflow...
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Build Python command based on arguments
|
||||||
|
set "PYTHON_CMD=python tools\run_development.py"
|
||||||
|
|
||||||
|
if defined ARG_LINT_ONLY (
|
||||||
|
set "PYTHON_CMD=!PYTHON_CMD! --lint"
|
||||||
|
echo 🔍 Running linting only...
|
||||||
|
) else if defined ARG_FORMAT_ONLY (
|
||||||
|
set "PYTHON_CMD=!PYTHON_CMD! --format"
|
||||||
|
echo 🎨 Running formatting only...
|
||||||
|
) else if defined ARG_TEST_ONLY (
|
||||||
|
set "PYTHON_CMD=!PYTHON_CMD! --test"
|
||||||
|
echo 🧪 Running tests only...
|
||||||
|
) else if defined ARG_STEPS (
|
||||||
|
set "PYTHON_CMD=!PYTHON_CMD! --steps !CUSTOM_STEPS!"
|
||||||
|
echo 🔄 Running custom workflow: !CUSTOM_STEPS!...
|
||||||
|
) else (
|
||||||
|
echo 🚀 Running complete development workflow: format + lint + test...
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Run the Python development workflow script
|
||||||
|
!PYTHON_CMD!
|
||||||
|
|
||||||
|
REM Capture exit code and display result
|
||||||
|
set WORKFLOW_RESULT=!errorlevel!
|
||||||
|
|
||||||
|
echo.
|
||||||
|
if !WORKFLOW_RESULT! equ 0 (
|
||||||
|
echo 🎉 Development workflow completed successfully!
|
||||||
|
) else (
|
||||||
|
echo ⚠️ Development workflow completed with issues.
|
||||||
|
echo Please review the output above and fix any problems.
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b !WORKFLOW_RESULT!
|
||||||
240
run_dev.sh
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Skelly Development Tools Runner
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# This script runs development tools for the Skelly Godot project.
|
||||||
|
# By default, it runs all checks: linting, formatting, and testing.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./run_dev.sh - Run all checks (lint + format + test)
|
||||||
|
# ./run_dev.sh --lint - Run only linting
|
||||||
|
# ./run_dev.sh --format - Run only formatting
|
||||||
|
# ./run_dev.sh --test - Run only tests
|
||||||
|
# ./run_dev.sh --help - Show this help message
|
||||||
|
# ./run_dev.sh --steps lint test - Run specific steps in order
|
||||||
|
#
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Initialize variables
|
||||||
|
ARG_LINT_ONLY=""
|
||||||
|
ARG_FORMAT_ONLY=""
|
||||||
|
ARG_TEST_ONLY=""
|
||||||
|
ARG_HELP=""
|
||||||
|
ARG_STEPS=""
|
||||||
|
CUSTOM_STEPS=""
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--lint)
|
||||||
|
ARG_LINT_ONLY=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--format)
|
||||||
|
ARG_FORMAT_ONLY=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--test)
|
||||||
|
ARG_TEST_ONLY=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help)
|
||||||
|
ARG_HELP=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--steps)
|
||||||
|
ARG_STEPS=1
|
||||||
|
shift
|
||||||
|
# Collect remaining arguments as custom steps
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
if [[ -z "$CUSTOM_STEPS" ]]; then
|
||||||
|
CUSTOM_STEPS="$1"
|
||||||
|
else
|
||||||
|
CUSTOM_STEPS="$CUSTOM_STEPS $1"
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "❌ Unknown argument: $1"
|
||||||
|
echo "Use --help for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Show help if requested
|
||||||
|
if [[ -n "$ARG_HELP" ]]; then
|
||||||
|
echo
|
||||||
|
echo "🔧 Skelly Development Tools Runner"
|
||||||
|
echo
|
||||||
|
echo "Usage:"
|
||||||
|
echo " ./run_dev.sh - Run all checks (lint + format + test)"
|
||||||
|
echo " ./run_dev.sh --lint - Run only linting"
|
||||||
|
echo " ./run_dev.sh --format - Run only formatting"
|
||||||
|
echo " ./run_dev.sh --test - Run only tests"
|
||||||
|
echo " ./run_dev.sh --help - Show this help message"
|
||||||
|
echo " ./run_dev.sh --steps lint test - Run specific steps in order"
|
||||||
|
echo
|
||||||
|
echo "Available steps for --steps:"
|
||||||
|
echo " lint - Run GDScript linting (gdlint)"
|
||||||
|
echo " format - Run GDScript formatting (gdformat)"
|
||||||
|
echo " test - Run Godot tests"
|
||||||
|
echo
|
||||||
|
echo "Examples:"
|
||||||
|
echo " ./run_dev.sh (runs lint, format, test)"
|
||||||
|
echo " ./run_dev.sh --lint (runs only linting)"
|
||||||
|
echo " ./run_dev.sh --steps format lint (runs format then lint)"
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "================================"
|
||||||
|
echo "🚀 Development Tools Runner"
|
||||||
|
echo "================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Check if Python is available
|
||||||
|
if ! command -v python3 &> /dev/null && ! command -v python &> /dev/null; then
|
||||||
|
echo "❌ ERROR: Python is not installed or not in PATH"
|
||||||
|
echo
|
||||||
|
echo "Installation instructions:"
|
||||||
|
echo "1. Ubuntu/Debian: sudo apt update && sudo apt install python3 python3-pip"
|
||||||
|
echo "2. macOS: brew install python"
|
||||||
|
echo "3. Or download from: https://python.org/downloads"
|
||||||
|
echo "4. Restart your terminal"
|
||||||
|
echo "5. Run this script again"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use python3 if available, otherwise python
|
||||||
|
PYTHON_CMD="python3"
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
PYTHON_CMD="python"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if pip is available
|
||||||
|
if ! command -v pip3 &> /dev/null && ! command -v pip &> /dev/null; then
|
||||||
|
echo "❌ ERROR: pip is not installed or not in PATH"
|
||||||
|
echo "Please ensure Python was installed correctly with pip"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use pip3 if available, otherwise pip
|
||||||
|
PIP_CMD="pip3"
|
||||||
|
if ! command -v pip3 &> /dev/null; then
|
||||||
|
PIP_CMD="pip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if Godot is available (only if test step will be run)
|
||||||
|
NEED_GODOT=""
|
||||||
|
if [[ -n "$ARG_TEST_ONLY" ]]; then
|
||||||
|
NEED_GODOT=1
|
||||||
|
fi
|
||||||
|
if [[ -n "$ARG_STEPS" ]] && [[ "$CUSTOM_STEPS" == *"test"* ]]; then
|
||||||
|
NEED_GODOT=1
|
||||||
|
fi
|
||||||
|
if [[ -z "$ARG_LINT_ONLY" && -z "$ARG_FORMAT_ONLY" && -z "$ARG_STEPS" ]]; then
|
||||||
|
NEED_GODOT=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NEED_GODOT" ]]; then
|
||||||
|
if ! command -v godot &> /dev/null; then
|
||||||
|
echo "❌ ERROR: Godot is not installed or not in PATH"
|
||||||
|
echo
|
||||||
|
echo "Installation instructions:"
|
||||||
|
echo "1. Download Godot from https://godotengine.org/download"
|
||||||
|
echo "2. Add Godot executable to your PATH environment variable"
|
||||||
|
echo "3. Or place godot executable in this project directory"
|
||||||
|
echo "4. Restart your terminal"
|
||||||
|
echo "5. Run this script again"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if gdlint and gdformat are available (only if needed)
|
||||||
|
NEED_GDTOOLS=""
|
||||||
|
if [[ -n "$ARG_LINT_ONLY" ]]; then
|
||||||
|
NEED_GDTOOLS=1
|
||||||
|
fi
|
||||||
|
if [[ -n "$ARG_FORMAT_ONLY" ]]; then
|
||||||
|
NEED_GDTOOLS=1
|
||||||
|
fi
|
||||||
|
if [[ -n "$ARG_STEPS" ]] && ([[ "$CUSTOM_STEPS" == *"lint"* ]] || [[ "$CUSTOM_STEPS" == *"format"* ]]); then
|
||||||
|
NEED_GDTOOLS=1
|
||||||
|
fi
|
||||||
|
if [[ -z "$ARG_TEST_ONLY" && -z "$ARG_STEPS" ]]; then
|
||||||
|
NEED_GDTOOLS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NEED_GDTOOLS" ]]; then
|
||||||
|
if ! command -v gdlint &> /dev/null; then
|
||||||
|
echo "❌ ERROR: gdlint is not installed or not in PATH"
|
||||||
|
echo
|
||||||
|
echo "Installation instructions:"
|
||||||
|
echo "1. $PIP_CMD install --upgrade \"setuptools<81\""
|
||||||
|
echo "2. $PIP_CMD install gdtoolkit==4"
|
||||||
|
echo "3. Restart your terminal"
|
||||||
|
echo "4. Run this script again"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v gdformat &> /dev/null; then
|
||||||
|
echo "❌ ERROR: gdformat is not installed or not in PATH"
|
||||||
|
echo
|
||||||
|
echo "Installation instructions:"
|
||||||
|
echo "1. $PIP_CMD install --upgrade \"setuptools<81\""
|
||||||
|
echo "2. $PIP_CMD install gdtoolkit==4"
|
||||||
|
echo "3. Restart your terminal"
|
||||||
|
echo "4. Run this script again"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ All dependencies are available. Running development workflow..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Build Python command based on arguments
|
||||||
|
PYTHON_FULL_CMD="$PYTHON_CMD tools/run_development.py"
|
||||||
|
|
||||||
|
if [[ -n "$ARG_LINT_ONLY" ]]; then
|
||||||
|
PYTHON_FULL_CMD="$PYTHON_FULL_CMD --lint"
|
||||||
|
echo "🔍 Running linting only..."
|
||||||
|
elif [[ -n "$ARG_FORMAT_ONLY" ]]; then
|
||||||
|
PYTHON_FULL_CMD="$PYTHON_FULL_CMD --format"
|
||||||
|
echo "🎨 Running formatting only..."
|
||||||
|
elif [[ -n "$ARG_TEST_ONLY" ]]; then
|
||||||
|
PYTHON_FULL_CMD="$PYTHON_FULL_CMD --test"
|
||||||
|
echo "🧪 Running tests only..."
|
||||||
|
elif [[ -n "$ARG_STEPS" ]]; then
|
||||||
|
PYTHON_FULL_CMD="$PYTHON_FULL_CMD --steps $CUSTOM_STEPS"
|
||||||
|
echo "🔄 Running custom workflow: $CUSTOM_STEPS..."
|
||||||
|
else
|
||||||
|
echo "🚀 Running complete development workflow: format + lint + test..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Run the Python development workflow script
|
||||||
|
$PYTHON_FULL_CMD
|
||||||
|
|
||||||
|
# Capture exit code and display result
|
||||||
|
WORKFLOW_RESULT=$?
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [[ $WORKFLOW_RESULT -eq 0 ]]; then
|
||||||
|
echo "🎉 Development workflow completed successfully!"
|
||||||
|
else
|
||||||
|
echo "⚠️ Development workflow completed with issues."
|
||||||
|
echo "Please review the output above and fix any problems."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
exit $WORKFLOW_RESULT
|
||||||
152
scenes/game/Game.gd
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
const GAMEPLAY_SCENES = {
|
||||||
|
"match3": "res://scenes/game/gameplays/Match3Gameplay.tscn",
|
||||||
|
"clickomania": "res://scenes/game/gameplays/ClickomaniaGameplay.tscn"
|
||||||
|
}
|
||||||
|
|
||||||
|
var current_gameplay_mode: String
|
||||||
|
var global_score: int = 0:
|
||||||
|
set = set_global_score
|
||||||
|
|
||||||
|
@onready var back_button: Button = $BackButtonContainer/BackButton
|
||||||
|
@onready var gameplay_container: Control = $GameplayContainer
|
||||||
|
@onready var score_display: Label = $UI/ScoreDisplay
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if not back_button.pressed.is_connected(_on_back_button_pressed):
|
||||||
|
back_button.pressed.connect(_on_back_button_pressed)
|
||||||
|
|
||||||
|
# GameManager will set the gameplay mode, don't set default here
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"Game _ready() completed, waiting for GameManager to set gameplay mode",
|
||||||
|
"Game"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func set_gameplay_mode(mode: String) -> void:
|
||||||
|
DebugManager.log_info(
|
||||||
|
"set_gameplay_mode called with mode: %s" % mode, "Game"
|
||||||
|
)
|
||||||
|
current_gameplay_mode = mode
|
||||||
|
await load_gameplay(mode)
|
||||||
|
DebugManager.log_info(
|
||||||
|
"set_gameplay_mode completed for mode: %s" % mode, "Game"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func load_gameplay(mode: String) -> void:
|
||||||
|
DebugManager.log_debug("Loading gameplay mode: %s" % mode, "Game")
|
||||||
|
|
||||||
|
# Clear existing gameplay and wait for removal
|
||||||
|
var existing_children = gameplay_container.get_children()
|
||||||
|
if existing_children.size() > 0:
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"Removing %d existing children" % existing_children.size(), "Game"
|
||||||
|
)
|
||||||
|
for child in existing_children:
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"Removing existing child: %s" % child.name, "Game"
|
||||||
|
)
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
# Wait for children to be properly removed from scene tree
|
||||||
|
await get_tree().process_frame
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Children removal complete, container count: %d"
|
||||||
|
% gameplay_container.get_child_count()
|
||||||
|
),
|
||||||
|
"Game"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load new gameplay
|
||||||
|
if GAMEPLAY_SCENES.has(mode):
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"Found scene path: %s" % GAMEPLAY_SCENES[mode], "Game"
|
||||||
|
)
|
||||||
|
var gameplay_scene = load(GAMEPLAY_SCENES[mode])
|
||||||
|
var gameplay_instance = gameplay_scene.instantiate()
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"Instantiated gameplay: %s" % gameplay_instance.name, "Game"
|
||||||
|
)
|
||||||
|
gameplay_container.add_child(gameplay_instance)
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Added gameplay to container, child count now: %d"
|
||||||
|
% gameplay_container.get_child_count()
|
||||||
|
),
|
||||||
|
"Game"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Connect gameplay signals to shared systems
|
||||||
|
if gameplay_instance.has_signal("score_changed"):
|
||||||
|
gameplay_instance.score_changed.connect(_on_score_changed)
|
||||||
|
DebugManager.log_debug("Connected score_changed signal", "Game")
|
||||||
|
else:
|
||||||
|
DebugManager.log_error(
|
||||||
|
"Gameplay mode '%s' not found in GAMEPLAY_SCENES" % mode, "Game"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func set_global_score(value: int) -> void:
|
||||||
|
global_score = value
|
||||||
|
if score_display:
|
||||||
|
score_display.text = "Score: " + str(global_score)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_score_changed(points: int) -> void:
|
||||||
|
self.global_score += points
|
||||||
|
SaveManager.update_current_score(self.global_score)
|
||||||
|
|
||||||
|
|
||||||
|
func get_global_score() -> int:
|
||||||
|
return global_score
|
||||||
|
|
||||||
|
|
||||||
|
func _get_current_gameplay_instance() -> Node:
|
||||||
|
if gameplay_container.get_child_count() > 0:
|
||||||
|
return gameplay_container.get_child(0)
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _on_back_button_pressed() -> void:
|
||||||
|
DebugManager.log_debug("Back button pressed in game scene", "Game")
|
||||||
|
AudioManager.play_ui_click()
|
||||||
|
|
||||||
|
# Save current grid state if we have an active match3 gameplay
|
||||||
|
var gameplay_instance = _get_current_gameplay_instance()
|
||||||
|
if gameplay_instance and gameplay_instance.has_method("save_current_state"):
|
||||||
|
DebugManager.log_info("Saving grid state before exit", "Game")
|
||||||
|
# Make sure the gameplay instance is still valid and not being destroyed
|
||||||
|
if (
|
||||||
|
is_instance_valid(gameplay_instance)
|
||||||
|
and gameplay_instance.is_inside_tree()
|
||||||
|
):
|
||||||
|
gameplay_instance.save_current_state()
|
||||||
|
else:
|
||||||
|
DebugManager.log_warn(
|
||||||
|
"Gameplay instance invalid, skipping grid save on exit", "Game"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save the current score immediately before exiting
|
||||||
|
SaveManager.finish_game(global_score)
|
||||||
|
GameManager.exit_to_main_menu()
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if event.is_action_pressed("ui_back"):
|
||||||
|
# Handle gamepad/keyboard back action - same as back button
|
||||||
|
_on_back_button_pressed()
|
||||||
|
elif (
|
||||||
|
event.is_action_pressed("action_south")
|
||||||
|
and Input.is_action_pressed("action_north")
|
||||||
|
):
|
||||||
|
# Debug: Switch to clickomania when primary+secondary actions pressed together
|
||||||
|
if current_gameplay_mode == "match3":
|
||||||
|
set_gameplay_mode("clickomania")
|
||||||
|
DebugManager.log_debug("Switched to clickomania gameplay", "Game")
|
||||||
|
else:
|
||||||
|
set_gameplay_mode("match3")
|
||||||
|
DebugManager.log_debug("Switched to match3 gameplay", "Game")
|
||||||
77
scenes/game/Game.tscn
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://dmctbuggudsx2"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bs4veuda3h358" path="res://scenes/game/game.gd" id="1_uwrxv"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/ui/DebugToggle.tscn" id="3_debug"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bengv32u1jeym" path="res://assets/textures/backgrounds/BGx3.png" id="GlobalBackground"]
|
||||||
|
|
||||||
|
[node name="Game" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_uwrxv")
|
||||||
|
|
||||||
|
[node name="Background" type="TextureRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
texture = ExtResource("GlobalBackground")
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 1
|
||||||
|
|
||||||
|
[node name="UI" type="Control" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="ScoreDisplay" type="Label" parent="UI"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 2
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 10.0
|
||||||
|
offset_top = -31.0
|
||||||
|
offset_right = 110.0
|
||||||
|
offset_bottom = -10.0
|
||||||
|
grow_vertical = 0
|
||||||
|
text = "Score: 0"
|
||||||
|
|
||||||
|
[node name="GameplayContainer" type="Control" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="BackButtonContainer" type="Control" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 1
|
||||||
|
anchor_right = 0.0
|
||||||
|
anchor_bottom = 0.0
|
||||||
|
offset_left = 10.0
|
||||||
|
offset_top = 10.0
|
||||||
|
offset_right = 55.0
|
||||||
|
offset_bottom = 41.0
|
||||||
|
|
||||||
|
[node name="BackButton" type="Button" parent="BackButtonContainer"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
text = "back"
|
||||||
|
|
||||||
|
[node name="DebugToggle" parent="." instance=ExtResource("3_debug")]
|
||||||
|
layout_mode = 1
|
||||||
|
|
||||||
|
[connection signal="pressed" from="BackButtonContainer/BackButton" to="." method="_on_back_button_pressed"]
|
||||||
1
scenes/game/game.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bs4veuda3h358
|
||||||
11
scenes/game/gameplays/ClickomaniaGameplay.gd
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
signal score_changed(points: int)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
DebugManager.log_info("Clickomania gameplay loaded", "Clickomania")
|
||||||
|
# Example: Add some score after a few seconds to test the system
|
||||||
|
await get_tree().create_timer(2.0).timeout
|
||||||
|
score_changed.emit(100)
|
||||||
|
DebugManager.log_info("Clickomania awarded 100 points", "Clickomania")
|
||||||
1
scenes/game/gameplays/ClickomaniaGameplay.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bkheckv0upd82
|
||||||
21
scenes/game/gameplays/ClickomaniaGameplay.tscn
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cl7g8v0eh3mam"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/game/gameplays/ClickomaniaGameplay.gd" id="1_script"]
|
||||||
|
|
||||||
|
[node name="Clickomania" type="Node2D"]
|
||||||
|
script = ExtResource("1_script")
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="."]
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -100.0
|
||||||
|
offset_top = -12.0
|
||||||
|
offset_right = 100.0
|
||||||
|
offset_bottom = 12.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
text = "Clickomania Gameplay (Demo)"
|
||||||
|
horizontal_alignment = 1
|
||||||
1
scenes/game/gameplays/GameplayInputHandler.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cgkrsgxk0stw4
|
||||||
1
scenes/game/gameplays/GridGameplay.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://t8awjmas4wcg
|
||||||
46
scenes/game/gameplays/Match3DebugMenu.gd
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
extends DebugMenuBase
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# Set specific configuration for Match3DebugMenu
|
||||||
|
log_category = "Match3"
|
||||||
|
target_script_path = "res://scenes/game/gameplays/Match3Gameplay.gd"
|
||||||
|
|
||||||
|
# Call parent's _ready
|
||||||
|
super()
|
||||||
|
|
||||||
|
DebugManager.log_debug("Match3DebugMenu _ready() completed", log_category)
|
||||||
|
|
||||||
|
# Initialize with current debug state if enabled
|
||||||
|
var current_debug_state = DebugManager.is_debug_enabled()
|
||||||
|
if current_debug_state:
|
||||||
|
_on_debug_toggled(true)
|
||||||
|
|
||||||
|
|
||||||
|
func _find_target_scene():
|
||||||
|
# Debug menu is now: Match3 -> UILayer -> Match3DebugMenu
|
||||||
|
# So we need to go up two levels: get_parent() = UILayer, get_parent().get_parent() = Match3
|
||||||
|
var ui_layer = get_parent()
|
||||||
|
if ui_layer and ui_layer is CanvasLayer:
|
||||||
|
var potential_match3 = ui_layer.get_parent()
|
||||||
|
if potential_match3 and potential_match3.get_script():
|
||||||
|
var script_path = potential_match3.get_script().resource_path
|
||||||
|
if script_path == target_script_path:
|
||||||
|
match3_scene = potential_match3
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Found match3 scene: "
|
||||||
|
+ match3_scene.name
|
||||||
|
+ " at path: "
|
||||||
|
+ str(match3_scene.get_path())
|
||||||
|
),
|
||||||
|
log_category
|
||||||
|
)
|
||||||
|
_update_ui_from_scene()
|
||||||
|
_stop_search_timer()
|
||||||
|
return
|
||||||
|
|
||||||
|
# If we couldn't find it, clear the reference and continue searching
|
||||||
|
match3_scene = null
|
||||||
|
DebugManager.log_error("Could not find match3_gameplay scene", log_category)
|
||||||
|
_start_search_timer()
|
||||||
1
scenes/game/gameplays/Match3DebugMenu.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ccfms5oiub35j
|
||||||
83
scenes/game/gameplays/Match3DebugMenu.tscn
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://b76oiwlifikl3"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/game/gameplays/Match3DebugMenu.gd" id="1_debug_menu"]
|
||||||
|
|
||||||
|
[node name="DebugMenu" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 1
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = -201.0
|
||||||
|
offset_top = 59.0
|
||||||
|
offset_right = -11.0
|
||||||
|
offset_bottom = 169.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
script = ExtResource("1_debug_menu")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 10.0
|
||||||
|
offset_top = 10.0
|
||||||
|
offset_right = -10.0
|
||||||
|
offset_bottom = -10.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Match-3 Debug Menu"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="RegenerateButton" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Generate New Grid"
|
||||||
|
|
||||||
|
[node name="GemTypesContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="GemTypesLabel" type="Label" parent="VBoxContainer/GemTypesContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Gem Types: 5"
|
||||||
|
|
||||||
|
[node name="GemTypesSpinBox" type="SpinBox" parent="VBoxContainer/GemTypesContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
min_value = 3.0
|
||||||
|
max_value = 8.0
|
||||||
|
value = 5.0
|
||||||
|
|
||||||
|
[node name="GridSizeContainer" type="VBoxContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="GridSizeLabel" type="Label" parent="VBoxContainer/GridSizeContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Grid Size"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="GridWidthContainer" type="HBoxContainer" parent="VBoxContainer/GridSizeContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="GridWidthLabel" type="Label" parent="VBoxContainer/GridSizeContainer/GridWidthContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Width: 8"
|
||||||
|
|
||||||
|
[node name="GridWidthSpinBox" type="SpinBox" parent="VBoxContainer/GridSizeContainer/GridWidthContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
min_value = 4.0
|
||||||
|
max_value = 12.0
|
||||||
|
value = 8.0
|
||||||
|
|
||||||
|
[node name="GridHeightContainer" type="HBoxContainer" parent="VBoxContainer/GridSizeContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="GridHeightLabel" type="Label" parent="VBoxContainer/GridSizeContainer/GridHeightContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Height: 8"
|
||||||
|
|
||||||
|
[node name="GridHeightSpinBox" type="SpinBox" parent="VBoxContainer/GridSizeContainer/GridHeightContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
min_value = 4.0
|
||||||
|
max_value = 12.0
|
||||||
|
value = 8.0
|
||||||
1399
scenes/game/gameplays/Match3Gameplay.gd
Normal file
1
scenes/game/gameplays/Match3Gameplay.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dbbi8ooysxp7f
|
||||||
13
scenes/game/gameplays/Match3Gameplay.tscn
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://b4kv7g7kllwgb"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://o8crf6688lan" path="res://scenes/game/gameplays/Match3Gameplay.gd" id="1_mvfdp"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b76oiwlifikl3" path="res://scenes/game/gameplays/Match3DebugMenu.tscn" id="2_debug_menu"]
|
||||||
|
|
||||||
|
[node name="Match3" type="Node2D"]
|
||||||
|
script = ExtResource("1_mvfdp")
|
||||||
|
|
||||||
|
[node name="GridContainer" type="Node2D" parent="."]
|
||||||
|
|
||||||
|
[node name="UILayer" type="CanvasLayer" parent="."]
|
||||||
|
|
||||||
|
[node name="Match3DebugMenu" parent="UILayer" instance=ExtResource("2_debug_menu")]
|
||||||
87
scenes/game/gameplays/Match3InputHandler.gd
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
class_name Match3InputHandler
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Mouse input handler for Match3 gameplay
|
||||||
|
##
|
||||||
|
## Static methods for handling mouse interactions in Match3 games.
|
||||||
|
## Converts between world coordinates and grid positions, performs hit detection on tiles.
|
||||||
|
##
|
||||||
|
## Usage:
|
||||||
|
## var tile = Match3InputHandler.find_tile_at_position(grid, grid_size, mouse_pos)
|
||||||
|
## var grid_pos = Match3InputHandler.get_grid_position_from_world(node, world_pos, offset, size)
|
||||||
|
|
||||||
|
|
||||||
|
static func find_tile_at_position(
|
||||||
|
grid: Array, grid_size: Vector2i, world_pos: Vector2
|
||||||
|
) -> Node2D:
|
||||||
|
## Find the tile that contains the world position.
|
||||||
|
##
|
||||||
|
## Iterates through all tiles and checks if the world position falls within
|
||||||
|
## any tile's sprite boundaries.
|
||||||
|
##
|
||||||
|
## Args:
|
||||||
|
## grid: 2D array of tile nodes arranged in [y][x] format
|
||||||
|
## grid_size: Dimensions of the grid (width x height)
|
||||||
|
## world_pos: World coordinates to test
|
||||||
|
##
|
||||||
|
## Returns:
|
||||||
|
## The first tile node that contains the position, or null if no tile found
|
||||||
|
for y in range(grid_size.y):
|
||||||
|
for x in range(grid_size.x):
|
||||||
|
if y < grid.size() and x < grid[y].size():
|
||||||
|
var tile = grid[y][x]
|
||||||
|
if tile and tile.has_node("Sprite2D"):
|
||||||
|
var sprite = tile.get_node("Sprite2D")
|
||||||
|
if sprite and sprite.texture:
|
||||||
|
var sprite_bounds = get_sprite_world_bounds(
|
||||||
|
tile, sprite
|
||||||
|
)
|
||||||
|
if is_point_inside_rect(world_pos, sprite_bounds):
|
||||||
|
return tile
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
static func get_sprite_world_bounds(tile: Node2D, sprite: Sprite2D) -> Rect2:
|
||||||
|
## Calculate the world space bounding rectangle of a sprite.
|
||||||
|
##
|
||||||
|
## Args:
|
||||||
|
## tile: The tile node containing the sprite
|
||||||
|
## sprite: The Sprite2D node to calculate bounds for
|
||||||
|
##
|
||||||
|
## Returns:
|
||||||
|
## Rect2 representing the sprite's bounds in world coordinates
|
||||||
|
var texture_size = sprite.texture.get_size()
|
||||||
|
var actual_size = texture_size * sprite.scale
|
||||||
|
var half_size = actual_size * 0.5
|
||||||
|
var top_left = tile.position - half_size
|
||||||
|
return Rect2(top_left, actual_size)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_point_inside_rect(point: Vector2, rect: Rect2) -> bool:
|
||||||
|
# Check if a point is inside a rectangle
|
||||||
|
return (
|
||||||
|
point.x >= rect.position.x
|
||||||
|
and point.x <= rect.position.x + rect.size.x
|
||||||
|
and point.y >= rect.position.y
|
||||||
|
and point.y <= rect.position.y + rect.size.y
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_grid_position_from_world(
|
||||||
|
node: Node2D, world_pos: Vector2, grid_offset: Vector2, tile_size: float
|
||||||
|
) -> Vector2i:
|
||||||
|
## Convert world coordinates to grid array indices.
|
||||||
|
##
|
||||||
|
## Args:
|
||||||
|
## node: Reference node for coordinate space conversion
|
||||||
|
## world_pos: Position in world coordinates to convert
|
||||||
|
## grid_offset: Offset of the grid's origin from the node's position
|
||||||
|
## tile_size: Size of each tile in world units
|
||||||
|
##
|
||||||
|
## Returns:
|
||||||
|
## Vector2i containing the grid coordinates (x, y) for array indexing
|
||||||
|
var local_pos = node.to_local(world_pos)
|
||||||
|
var relative_pos = local_pos - grid_offset
|
||||||
|
var grid_x = int(relative_pos.x / tile_size)
|
||||||
|
var grid_y = int(relative_pos.y / tile_size)
|
||||||
|
return Vector2i(grid_x, grid_y)
|
||||||
1
scenes/game/gameplays/Match3InputHandler.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ogm8w7l6bhif
|
||||||
151
scenes/game/gameplays/Match3SaveManager.gd
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
class_name Match3SaveManager
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Save/Load manager for Match3 gameplay state
|
||||||
|
##
|
||||||
|
## Handles serialization and deserialization of Match3 game state.
|
||||||
|
## Converts game objects to data structures for storage and restoration.
|
||||||
|
##
|
||||||
|
## Usage:
|
||||||
|
## # Save current state
|
||||||
|
## var grid_data = Match3SaveManager.serialize_grid_state(game_grid, grid_size)
|
||||||
|
##
|
||||||
|
## # Restore previous state
|
||||||
|
## var success = Match3SaveManager.deserialize_grid_state(grid_data, game_grid, grid_size)
|
||||||
|
|
||||||
|
|
||||||
|
static func serialize_grid_state(grid: Array, grid_size: Vector2i) -> Array:
|
||||||
|
## Convert the current game grid to a serializable 2D array of tile types.
|
||||||
|
##
|
||||||
|
## Extracts the tile_type property from each tile node and creates a 2D array
|
||||||
|
## that can be saved to disk. Invalid or missing tiles are represented as -1.
|
||||||
|
##
|
||||||
|
## Args:
|
||||||
|
## grid: The current game grid (2D array of tile nodes)
|
||||||
|
## grid_size: Dimensions of the grid to serialize
|
||||||
|
##
|
||||||
|
## Returns:
|
||||||
|
## Array: 2D array where each element is either a tile type (int) or -1 for empty
|
||||||
|
var serialized_grid = []
|
||||||
|
var valid_tiles = 0
|
||||||
|
var null_tiles = 0
|
||||||
|
|
||||||
|
for y in range(grid_size.y):
|
||||||
|
var row = []
|
||||||
|
for x in range(grid_size.x):
|
||||||
|
if y < grid.size() and x < grid[y].size() and grid[y][x]:
|
||||||
|
row.append(grid[y][x].tile_type)
|
||||||
|
valid_tiles += 1
|
||||||
|
else:
|
||||||
|
row.append(-1) # Invalid/empty tile
|
||||||
|
null_tiles += 1
|
||||||
|
serialized_grid.append(row)
|
||||||
|
|
||||||
|
DebugManager.log_info(
|
||||||
|
(
|
||||||
|
"Serialized grid state: %dx%d grid, %d valid tiles, %d null tiles"
|
||||||
|
% [grid_size.x, grid_size.y, valid_tiles, null_tiles]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
return serialized_grid
|
||||||
|
|
||||||
|
|
||||||
|
static func get_active_gem_types_from_grid(
|
||||||
|
grid: Array, tile_types: int
|
||||||
|
) -> Array:
|
||||||
|
# Get active gem types from the first available tile
|
||||||
|
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
||||||
|
return grid[0][0].active_gem_types.duplicate()
|
||||||
|
|
||||||
|
# Fallback to default
|
||||||
|
var default_types = []
|
||||||
|
for i in range(tile_types):
|
||||||
|
default_types.append(i)
|
||||||
|
return default_types
|
||||||
|
|
||||||
|
|
||||||
|
static func save_game_state(grid: Array, grid_size: Vector2i, tile_types: int):
|
||||||
|
# Save complete game state
|
||||||
|
var grid_layout = serialize_grid_state(grid, grid_size)
|
||||||
|
var active_gems = get_active_gem_types_from_grid(grid, tile_types)
|
||||||
|
|
||||||
|
DebugManager.log_info(
|
||||||
|
(
|
||||||
|
"Saving match3 state: size(%d,%d), %d tile types, %d active gems"
|
||||||
|
% [grid_size.x, grid_size.y, tile_types, active_gems.size()]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
|
||||||
|
SaveManager.save_grid_state(grid_size, tile_types, active_gems, grid_layout)
|
||||||
|
|
||||||
|
|
||||||
|
static func restore_grid_from_layout(
|
||||||
|
match3_node: Node2D,
|
||||||
|
grid_layout: Array,
|
||||||
|
active_gems: Array[int],
|
||||||
|
grid_size: Vector2i,
|
||||||
|
tile_scene: PackedScene,
|
||||||
|
grid_offset: Vector2,
|
||||||
|
tile_size: float,
|
||||||
|
tile_types: int
|
||||||
|
) -> Array[Array]:
|
||||||
|
# Clear ALL existing tile children
|
||||||
|
var all_tile_children = []
|
||||||
|
for child in match3_node.get_children():
|
||||||
|
if child.has_method("get_script") and child.get_script():
|
||||||
|
var script_path = child.get_script().resource_path
|
||||||
|
if script_path == "res://scenes/game/gameplays/Tile.gd":
|
||||||
|
all_tile_children.append(child)
|
||||||
|
|
||||||
|
# Remove all found tile children
|
||||||
|
for child in all_tile_children:
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
# Wait for nodes to be freed
|
||||||
|
await match3_node.get_tree().process_frame
|
||||||
|
|
||||||
|
# Create new grid
|
||||||
|
var new_grid: Array[Array] = []
|
||||||
|
for y in range(grid_size.y):
|
||||||
|
new_grid.append(Array([]))
|
||||||
|
for x in range(grid_size.x):
|
||||||
|
var tile = tile_scene.instantiate()
|
||||||
|
var tile_position = grid_offset + Vector2(x, y) * tile_size
|
||||||
|
tile.position = tile_position
|
||||||
|
tile.grid_position = Vector2i(x, y)
|
||||||
|
|
||||||
|
match3_node.add_child(tile)
|
||||||
|
|
||||||
|
# Configure Area2D
|
||||||
|
tile.monitoring = true
|
||||||
|
tile.monitorable = true
|
||||||
|
tile.input_pickable = true
|
||||||
|
|
||||||
|
tile.set_tile_size(tile_size)
|
||||||
|
tile.set_active_gem_types(active_gems)
|
||||||
|
|
||||||
|
# Set the saved tile type
|
||||||
|
var saved_tile_type = grid_layout[y][x]
|
||||||
|
if saved_tile_type >= 0 and saved_tile_type < tile_types:
|
||||||
|
tile.tile_type = saved_tile_type
|
||||||
|
else:
|
||||||
|
tile.tile_type = randi() % tile_types
|
||||||
|
|
||||||
|
# Connect tile signals
|
||||||
|
if (
|
||||||
|
tile.has_signal("tile_selected")
|
||||||
|
and match3_node.has_method("_on_tile_selected")
|
||||||
|
):
|
||||||
|
tile.tile_selected.connect(match3_node._on_tile_selected)
|
||||||
|
if (
|
||||||
|
tile.has_signal("tile_hovered")
|
||||||
|
and match3_node.has_method("_on_tile_hovered")
|
||||||
|
):
|
||||||
|
tile.tile_hovered.connect(match3_node._on_tile_hovered)
|
||||||
|
tile.tile_unhovered.connect(match3_node._on_tile_unhovered)
|
||||||
|
|
||||||
|
new_grid[y].append(tile)
|
||||||
|
|
||||||
|
return new_grid
|
||||||
1
scenes/game/gameplays/Match3SaveManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://duheejfr6de6x
|
||||||
116
scenes/game/gameplays/Match3Validator.gd
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
class_name Match3Validator
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Validation utilities for Match3 gameplay
|
||||||
|
##
|
||||||
|
## Static methods for validating Match3 game state and data integrity.
|
||||||
|
## Prevents crashes by checking bounds, data structures, and game logic constraints.
|
||||||
|
##
|
||||||
|
## Usage:
|
||||||
|
## if Match3Validator.is_valid_grid_position(pos, grid_size):
|
||||||
|
## # Safe to access grid[pos.y][pos.x]
|
||||||
|
##
|
||||||
|
## if Match3Validator.validate_grid_integrity(grid, grid_size):
|
||||||
|
## # Grid structure is valid for game operations
|
||||||
|
|
||||||
|
|
||||||
|
static func is_valid_grid_position(pos: Vector2i, grid_size: Vector2i) -> bool:
|
||||||
|
## Check if the position is within the grid boundaries.
|
||||||
|
##
|
||||||
|
## Performs bounds checking to prevent index out of bounds errors.
|
||||||
|
##
|
||||||
|
## Args:
|
||||||
|
## pos: Grid position to validate (x, y coordinates)
|
||||||
|
## grid_size: Dimensions of the grid (width, height)
|
||||||
|
##
|
||||||
|
## Returns:
|
||||||
|
## bool: True if position is valid, False if out of bounds
|
||||||
|
return (
|
||||||
|
pos.x >= 0
|
||||||
|
and pos.y >= 0
|
||||||
|
and pos.x < grid_size.x
|
||||||
|
and pos.y < grid_size.y
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool:
|
||||||
|
## Verify that the grid array structure matches expected dimensions.
|
||||||
|
##
|
||||||
|
## Validates the grid's 2D array structure for safe game operations.
|
||||||
|
## Checks array types, dimensions, and structural consistency.
|
||||||
|
##
|
||||||
|
## Args:
|
||||||
|
## grid: The 2D array representing the game grid
|
||||||
|
## grid_size: Expected dimensions (width x height)
|
||||||
|
##
|
||||||
|
## Returns:
|
||||||
|
## bool: True if grid structure is valid, False if corrupted or malformed
|
||||||
|
if not grid is Array:
|
||||||
|
DebugManager.log_error("Grid is not an array", "Match3")
|
||||||
|
return false
|
||||||
|
|
||||||
|
if grid.size() != grid_size.y:
|
||||||
|
DebugManager.log_error(
|
||||||
|
"Grid height mismatch: %d vs %d" % [grid.size(), grid_size.y],
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
|
||||||
|
for y in range(grid.size()):
|
||||||
|
if not grid[y] is Array:
|
||||||
|
DebugManager.log_error("Grid row %d is not an array" % y, "Match3")
|
||||||
|
return false
|
||||||
|
|
||||||
|
if grid[y].size() != grid_size.x:
|
||||||
|
DebugManager.log_error(
|
||||||
|
(
|
||||||
|
"Grid row %d width mismatch: %d vs %d"
|
||||||
|
% [y, grid[y].size(), grid_size.x]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
static func safe_grid_access(
|
||||||
|
grid: Array, pos: Vector2i, grid_size: Vector2i
|
||||||
|
) -> Node2D:
|
||||||
|
# Safe grid access with comprehensive bounds checking
|
||||||
|
if not is_valid_grid_position(pos, grid_size):
|
||||||
|
return null
|
||||||
|
|
||||||
|
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
||||||
|
DebugManager.log_warn(
|
||||||
|
"Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3"
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
|
||||||
|
var tile = grid[pos.y][pos.x]
|
||||||
|
if not tile or not is_instance_valid(tile):
|
||||||
|
return null
|
||||||
|
|
||||||
|
return tile
|
||||||
|
|
||||||
|
|
||||||
|
static func safe_tile_access(tile: Node2D, property: String):
|
||||||
|
# Safe property access on tiles
|
||||||
|
if not tile or not is_instance_valid(tile):
|
||||||
|
return null
|
||||||
|
|
||||||
|
if not property in tile:
|
||||||
|
DebugManager.log_warn("Tile missing property: %s" % property, "Match3")
|
||||||
|
return null
|
||||||
|
|
||||||
|
return tile.get(property)
|
||||||
|
|
||||||
|
|
||||||
|
static func are_tiles_adjacent(tile1: Node2D, tile2: Node2D) -> bool:
|
||||||
|
if not tile1 or not tile2:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var pos1 = tile1.grid_position
|
||||||
|
var pos2 = tile2.grid_position
|
||||||
|
var diff = abs(pos1.x - pos2.x) + abs(pos1.y - pos2.y)
|
||||||
|
return diff == 1
|
||||||
1
scenes/game/gameplays/Match3Validator.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dy3aym6riijct
|
||||||
1
scenes/game/gameplays/SelectableItem.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bib676n6sm05v
|
||||||
308
scenes/game/gameplays/Tile.gd
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
signal tile_selected(tile: Node2D)
|
||||||
|
|
||||||
|
# Target size for each tile to fit in the 54x54 grid cells
|
||||||
|
const TILE_SIZE = 48 # Slightly smaller than 54 to leave some padding
|
||||||
|
|
||||||
|
@export var tile_type: int = 0:
|
||||||
|
set = _set_tile_type
|
||||||
|
|
||||||
|
var grid_position: Vector2i
|
||||||
|
var is_selected: bool = false:
|
||||||
|
set = _set_selected
|
||||||
|
var is_highlighted: bool = false:
|
||||||
|
set = _set_highlighted
|
||||||
|
var original_scale: Vector2 = Vector2.ONE # Store the original scale for the board
|
||||||
|
|
||||||
|
# All available gem textures
|
||||||
|
var all_gem_textures: Array[Texture2D] = [
|
||||||
|
preload("res://assets/sprites/skulls/red.png"),
|
||||||
|
preload("res://assets/sprites/skulls/blue.png"),
|
||||||
|
preload("res://assets/sprites/skulls/green.png"),
|
||||||
|
preload("res://assets/sprites/skulls/pink.png"),
|
||||||
|
preload("res://assets/sprites/skulls/purple.png"),
|
||||||
|
preload("res://assets/sprites/skulls/dark-blue.png"),
|
||||||
|
preload("res://assets/sprites/skulls/grey.png"),
|
||||||
|
preload("res://assets/sprites/skulls/orange.png"),
|
||||||
|
preload("res://assets/sprites/skulls/yellow.png"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Currently active gem types (indices into all_gem_textures)
|
||||||
|
var active_gem_types: Array[int] = [] # Will be set from TileManager
|
||||||
|
|
||||||
|
@onready var sprite: Sprite2D = $Sprite2D
|
||||||
|
|
||||||
|
|
||||||
|
func _set_tile_type(value: int) -> void:
|
||||||
|
tile_type = value
|
||||||
|
# Fixed: Add sprite null check to prevent crashes during initialization
|
||||||
|
if not sprite:
|
||||||
|
return
|
||||||
|
if value >= 0 and value < active_gem_types.size():
|
||||||
|
var texture_index = active_gem_types[value]
|
||||||
|
sprite.texture = all_gem_textures[texture_index]
|
||||||
|
_scale_sprite_to_fit()
|
||||||
|
else:
|
||||||
|
DebugManager.log_error(
|
||||||
|
(
|
||||||
|
"Invalid tile type: "
|
||||||
|
+ str(value)
|
||||||
|
+ ". Available types: 0-"
|
||||||
|
+ str(active_gem_types.size() - 1)
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _scale_sprite_to_fit() -> void:
|
||||||
|
# Fixed: Add additional null checks
|
||||||
|
if sprite and sprite.texture:
|
||||||
|
var texture_size = sprite.texture.get_size()
|
||||||
|
var max_dimension = max(texture_size.x, texture_size.y)
|
||||||
|
var scale_factor = TILE_SIZE / max_dimension
|
||||||
|
original_scale = Vector2(scale_factor, scale_factor)
|
||||||
|
sprite.scale = original_scale
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Set original scale to %s for tile (%d,%d)"
|
||||||
|
% [original_scale, grid_position.x, grid_position.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func set_active_gem_types(gem_indices: Array[int]) -> void:
|
||||||
|
if not gem_indices or gem_indices.is_empty():
|
||||||
|
DebugManager.log_error("Empty gem indices array provided", "Tile")
|
||||||
|
return
|
||||||
|
|
||||||
|
active_gem_types = gem_indices.duplicate()
|
||||||
|
|
||||||
|
# Validate all gem indices are within bounds
|
||||||
|
for gem_index in active_gem_types:
|
||||||
|
if gem_index < 0 or gem_index >= all_gem_textures.size():
|
||||||
|
DebugManager.log_error(
|
||||||
|
(
|
||||||
|
"Invalid gem index: %d (valid range: 0-%d)"
|
||||||
|
% [gem_index, all_gem_textures.size() - 1]
|
||||||
|
),
|
||||||
|
"Tile"
|
||||||
|
)
|
||||||
|
# Use default fallback
|
||||||
|
active_gem_types = [0, 1, 2, 3, 4]
|
||||||
|
break
|
||||||
|
|
||||||
|
# Re-validate current tile type
|
||||||
|
if tile_type >= active_gem_types.size():
|
||||||
|
# Generate a new random tile type within valid range
|
||||||
|
tile_type = randi() % active_gem_types.size()
|
||||||
|
|
||||||
|
_set_tile_type(tile_type)
|
||||||
|
|
||||||
|
|
||||||
|
func get_active_gem_count() -> int:
|
||||||
|
return active_gem_types.size()
|
||||||
|
|
||||||
|
|
||||||
|
func add_gem_type(gem_index: int) -> bool:
|
||||||
|
if gem_index < 0 or gem_index >= all_gem_textures.size():
|
||||||
|
DebugManager.log_error("Invalid gem index: %d" % gem_index, "Tile")
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not active_gem_types.has(gem_index):
|
||||||
|
active_gem_types.append(gem_index)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func remove_gem_type(gem_index: int) -> bool:
|
||||||
|
var type_index = active_gem_types.find(gem_index)
|
||||||
|
if type_index == -1:
|
||||||
|
return false
|
||||||
|
|
||||||
|
if active_gem_types.size() <= 2: # Keep at least 2 gem types
|
||||||
|
DebugManager.log_warn(
|
||||||
|
"Cannot remove gem type - minimum 2 types required", "Tile"
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
|
||||||
|
active_gem_types.erase(gem_index)
|
||||||
|
|
||||||
|
# Update tile if it was using the removed type
|
||||||
|
if tile_type >= active_gem_types.size():
|
||||||
|
tile_type = 0
|
||||||
|
_set_tile_type(tile_type)
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _set_selected(value: bool) -> void:
|
||||||
|
var old_value = is_selected
|
||||||
|
is_selected = value
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Tile (%d,%d) selection changed: %s -> %s"
|
||||||
|
% [grid_position.x, grid_position.y, old_value, value]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
_update_visual_feedback()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_highlighted(value: bool) -> void:
|
||||||
|
var old_value = is_highlighted
|
||||||
|
is_highlighted = value
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Tile (%d,%d) highlight changed: %s -> %s"
|
||||||
|
% [grid_position.x, grid_position.y, old_value, value]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
_update_visual_feedback()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_visual_feedback() -> void:
|
||||||
|
if not sprite:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Determine target values based on priority: selected > highlighted > normal
|
||||||
|
var target_modulate: Color
|
||||||
|
var target_scale: Vector2
|
||||||
|
var scale_multiplier: float
|
||||||
|
|
||||||
|
if is_selected:
|
||||||
|
# Selected: bright and larger than original board size
|
||||||
|
target_modulate = Color(1.2, 1.2, 1.2, 1.0)
|
||||||
|
scale_multiplier = UIConstants.TILE_SELECTED_SCALE
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"SELECTING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||||
|
% [
|
||||||
|
grid_position.x,
|
||||||
|
grid_position.y,
|
||||||
|
scale_multiplier,
|
||||||
|
sprite.scale
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
elif is_highlighted:
|
||||||
|
# Highlighted: subtle glow and larger than original board size
|
||||||
|
target_modulate = Color(1.1, 1.1, 1.1, 1.0)
|
||||||
|
scale_multiplier = UIConstants.TILE_HIGHLIGHTED_SCALE
|
||||||
|
(
|
||||||
|
DebugManager
|
||||||
|
. log_debug(
|
||||||
|
(
|
||||||
|
"HIGHLIGHTING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||||
|
% [
|
||||||
|
grid_position.x,
|
||||||
|
grid_position.y,
|
||||||
|
scale_multiplier,
|
||||||
|
sprite.scale
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Normal state: white and original board size
|
||||||
|
target_modulate = Color.WHITE
|
||||||
|
scale_multiplier = UIConstants.TILE_NORMAL_SCALE
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"NORMALIZING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||||
|
% [
|
||||||
|
grid_position.x,
|
||||||
|
grid_position.y,
|
||||||
|
scale_multiplier,
|
||||||
|
sprite.scale
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate target scale relative to original board scale
|
||||||
|
target_scale = original_scale * scale_multiplier
|
||||||
|
|
||||||
|
# Apply modulate immediately
|
||||||
|
sprite.modulate = target_modulate
|
||||||
|
|
||||||
|
# Only animate scale if it's actually changing
|
||||||
|
if sprite.scale != target_scale:
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Animating scale from %s to %s for tile (%d,%d)"
|
||||||
|
% [sprite.scale, target_scale, grid_position.x, grid_position.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tween = create_tween()
|
||||||
|
tween.tween_property(sprite, "scale", target_scale, 0.15)
|
||||||
|
|
||||||
|
# Add completion callback for debugging
|
||||||
|
tween.tween_callback(_on_scale_animation_completed.bind(target_scale))
|
||||||
|
else:
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"No scale change needed for tile (%d,%d)"
|
||||||
|
% [grid_position.x, grid_position.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_scale_animation_completed(expected_scale: Vector2) -> void:
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Scale animation completed for tile (%d,%d): expected %s, actual %s"
|
||||||
|
% [grid_position.x, grid_position.y, expected_scale, sprite.scale]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func force_reset_visual_state() -> void:
|
||||||
|
# Force reset all visual states - debug function
|
||||||
|
is_selected = false
|
||||||
|
is_highlighted = false
|
||||||
|
if sprite:
|
||||||
|
sprite.modulate = Color.WHITE
|
||||||
|
sprite.scale = original_scale # Reset to original board scale, not 1.0
|
||||||
|
DebugManager.log_debug(
|
||||||
|
(
|
||||||
|
"Forced visual reset on tile (%d,%d) to original scale %s"
|
||||||
|
% [grid_position.x, grid_position.y, original_scale]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Handle input for tile selection
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||||
|
# Check if the mouse click is within the tile's bounds
|
||||||
|
var local_position = to_local(get_global_mouse_position())
|
||||||
|
var sprite_rect = Rect2(
|
||||||
|
-TILE_SIZE / 2.0, -TILE_SIZE / 2.0, TILE_SIZE, TILE_SIZE
|
||||||
|
)
|
||||||
|
|
||||||
|
if sprite_rect.has_point(local_position):
|
||||||
|
tile_selected.emit(self)
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group("tiles") # Add to group for gem pool management
|
||||||
|
|
||||||
|
# Initialize with default gem pool if not already set
|
||||||
|
if active_gem_types.is_empty():
|
||||||
|
active_gem_types = [0, 1, 2, 3, 4] # Default to first 5 gems
|
||||||
|
|
||||||
|
_set_tile_type(tile_type)
|
||||||
8
scenes/game/gameplays/Tile.tscn
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bnk1gqom3oi6q"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/game/gameplays/Tile.gd" id="1_tile_script"]
|
||||||
|
|
||||||
|
[node name="Tile" type="Node2D"]
|
||||||
|
script = ExtResource("1_tile_script")
|
||||||
|
|
||||||
|
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||||
1
scenes/game/gameplays/match3_gameplay.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://o8crf6688lan
|
||||||
1
scenes/game/gameplays/match3_input_handler.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bgygx6iofwqwc
|
||||||
1
scenes/game/gameplays/match3_save_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://balbki1cnwdn1
|
||||||
1
scenes/game/gameplays/match3_validator.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cjav8g5js6umr
|
||||||
1
scenes/game/gameplays/tile.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ctdlvwin7q2cc
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
[gd_scene load_steps=3 format=3 uid="uid://ci2gk11211n0d"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://cwlop1ettlqhg" path="res://scripts/Main.gd" id="1_0wfyh"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://gbe1jarrwqsi" path="res://ui/PressAnyKeyScreen.tscn" id="1_o5qli"]
|
|
||||||
|
|
||||||
[node name="main" type="Control"]
|
|
||||||
layout_mode = 3
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
script = ExtResource("1_0wfyh")
|
|
||||||
|
|
||||||
[node name="PressAnyKeyScreen" parent="." instance=ExtResource("1_o5qli")]
|
|
||||||
layout_mode = 1
|
|
||||||
102
scenes/main/Main.gd
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
const MAIN_MENU_SCENE = preload("res://scenes/ui/MainMenu.tscn")
|
||||||
|
const SETTINGS_MENU_SCENE = preload("res://scenes/ui/SettingsMenu.tscn")
|
||||||
|
|
||||||
|
var current_menu: Control = null
|
||||||
|
|
||||||
|
@onready var splash_screen: Node = $SplashScreen
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
DebugManager.log_debug("Main scene ready", "Main")
|
||||||
|
# Use alternative connection method with input handling
|
||||||
|
_setup_splash_screen_connection()
|
||||||
|
|
||||||
|
|
||||||
|
func _setup_splash_screen_connection() -> void:
|
||||||
|
# Wait for all nodes to be ready
|
||||||
|
await get_tree().process_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
|
||||||
|
# Try to find SplashScreen node
|
||||||
|
splash_screen = get_node_or_null("SplashScreen")
|
||||||
|
if not splash_screen:
|
||||||
|
DebugManager.log_warn(
|
||||||
|
"SplashScreen node not found, trying alternative methods", "Main"
|
||||||
|
)
|
||||||
|
# Try to find by class or group
|
||||||
|
var splash_nodes = get_tree().get_nodes_in_group("localizable")
|
||||||
|
for node in splash_nodes:
|
||||||
|
if node.scene_file_path.ends_with("SplashScreen.tscn"):
|
||||||
|
splash_screen = node
|
||||||
|
break
|
||||||
|
|
||||||
|
if splash_screen:
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"SplashScreen node found: %s" % splash_screen.name, "Main"
|
||||||
|
)
|
||||||
|
# Try connecting to the signal if it exists
|
||||||
|
if splash_screen.has_signal("confirm_pressed"):
|
||||||
|
splash_screen.confirm_pressed.connect(_on_confirm_pressed)
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"Connected to confirm_pressed signal", "Main"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Fallback: use input handling directly on the main scene
|
||||||
|
DebugManager.log_warn("Using fallback input handling", "Main")
|
||||||
|
_use_fallback_input_handling()
|
||||||
|
else:
|
||||||
|
DebugManager.log_error("Could not find SplashScreen node", "Main")
|
||||||
|
_use_fallback_input_handling()
|
||||||
|
|
||||||
|
|
||||||
|
func _use_fallback_input_handling() -> void:
|
||||||
|
# Fallback: handle input directly in the main scene
|
||||||
|
set_process_unhandled_input(true)
|
||||||
|
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if splash_screen and splash_screen.is_inside_tree():
|
||||||
|
# Forward input to splash screen or handle directly
|
||||||
|
if event.is_action_pressed("action_confirm"):
|
||||||
|
_on_confirm_pressed()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_confirm_pressed() -> void:
|
||||||
|
DebugManager.log_debug("Transitioning to main menu", "Main")
|
||||||
|
splash_screen.queue_free()
|
||||||
|
show_main_menu()
|
||||||
|
|
||||||
|
|
||||||
|
func show_main_menu() -> void:
|
||||||
|
clear_current_menu()
|
||||||
|
var main_menu = MAIN_MENU_SCENE.instantiate()
|
||||||
|
main_menu.open_settings.connect(_on_open_settings)
|
||||||
|
add_child(main_menu)
|
||||||
|
current_menu = main_menu
|
||||||
|
|
||||||
|
|
||||||
|
func show_settings_menu() -> void:
|
||||||
|
clear_current_menu()
|
||||||
|
var settings_menu = SETTINGS_MENU_SCENE.instantiate()
|
||||||
|
settings_menu.back_to_main_menu.connect(_on_back_to_main_menu)
|
||||||
|
add_child(settings_menu)
|
||||||
|
current_menu = settings_menu
|
||||||
|
|
||||||
|
|
||||||
|
func clear_current_menu() -> void:
|
||||||
|
if current_menu:
|
||||||
|
current_menu.queue_free()
|
||||||
|
current_menu = null
|
||||||
|
|
||||||
|
|
||||||
|
func _on_open_settings() -> void:
|
||||||
|
DebugManager.log_debug("Opening settings menu", "Main")
|
||||||
|
show_settings_menu()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_back_to_main_menu() -> void:
|
||||||
|
DebugManager.log_debug("Back to main menu", "Main")
|
||||||
|
show_main_menu()
|
||||||
1
scenes/main/Main.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://rvuchiy0guv3
|
||||||
31
scenes/main/Main.tscn
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://podhr4b5aq2v"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://rvuchiy0guv3" path="res://scenes/main/Main.gd" id="1_0wfyh"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://gbe1jarrwqsi" path="res://scenes/main/SplashScreen.tscn" id="1_o5qli"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://df2b4wn8j6cxl" path="res://scenes/ui/DebugToggle.tscn" id="4_v7g8d"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bengv32u1jeym" path="res://assets/textures/backgrounds/BGx3.png" id="GlobalBackground"]
|
||||||
|
|
||||||
|
[node name="main" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_0wfyh")
|
||||||
|
|
||||||
|
[node name="Background" type="TextureRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
texture = ExtResource("GlobalBackground")
|
||||||
|
stretch_mode = 1
|
||||||
|
|
||||||
|
[node name="SplashScreen" parent="." instance=ExtResource("1_o5qli")]
|
||||||
|
layout_mode = 1
|
||||||
|
|
||||||
|
[node name="DebugToggle" parent="." instance=ExtResource("4_v7g8d")]
|
||||||
|
layout_mode = 1
|
||||||
27
scenes/main/SplashScreen.gd
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
signal confirm_pressed
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
DebugManager.log_debug("SplashScreen ready", "SplashScreen")
|
||||||
|
update_text()
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if (
|
||||||
|
event.is_action_pressed("action_confirm")
|
||||||
|
or event is InputEventScreenTouch
|
||||||
|
or (
|
||||||
|
event is InputEventMouseButton
|
||||||
|
and event.button_index == MOUSE_BUTTON_LEFT
|
||||||
|
and event.pressed
|
||||||
|
)
|
||||||
|
):
|
||||||
|
DebugManager.log_debug("Action pressed: " + str(event), "SplashScreen")
|
||||||
|
confirm_pressed.emit()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
func update_text() -> void:
|
||||||
|
$SplashContainer/ContinueLabel.text = tr("press_ok_continue")
|
||||||