Skip to content

Latest commit

 

History

History
521 lines (366 loc) · 10.8 KB

File metadata and controls

521 lines (366 loc) · 10.8 KB

🤝 Contributing Guide

Thank you for contributing to the OpenClaw Godot Plugin!

Table of Contents

  1. Ways to Contribute
  2. Development Environment Setup
  3. Code Contributions
  4. Pull Request Guidelines
  5. Issue Reporting
  6. Code Review Process
  7. Community Guidelines

Ways to Contribute

Contribution Areas

Area Description Difficulty
🐛 Bug Fixes Resolve issues, improve stability
📝 Documentation README, examples, translations
🔧 New Tools Add features to tools.gd ⭐⭐
⚡ Performance Optimization, memory management ⭐⭐⭐
🏗️ Architecture Core structural changes ⭐⭐⭐

Recommended Issues for First-Time Contributors

Check GitHub for good first issue label:

https://github.com/TomLeeLive/openclaw-godot-plugin/labels/good%20first%20issue

Development Environment Setup

1. Fork & Clone

# 1. Fork on GitHub

# 2. Clone
git clone https://github.com/YOUR_USERNAME/openclaw-godot-plugin.git
cd openclaw-godot-plugin

# 3. Add upstream remote
git remote add upstream https://github.com/TomLeeLive/openclaw-godot-plugin.git

2. Set Up Test Environment

# Create Godot test project
mkdir -p ~/godot-dev
cp -r addons ~/godot-dev/

# Install gateway extension
cp -r OpenClawPlugin~/* ~/.openclaw/extensions/godot/
openclaw gateway restart

3. Branch Strategy

# Feature development
git checkout -b feature/audio-tools

# Bug fix
git checkout -b fix/connection-timeout

# Documentation
git checkout -b docs/korean-translation

Code Contributions

Example 1: Adding a New Tool (animation.play)

Step 1: Check/Create Issue

## Feature Request: animation.play tool

### Description
Tool to control AnimationPlayer nodes

### Use Cases
- AI tests character animations
- Preview cinematic sequences

### Proposed API
animation.play {path: "Player/AnimationPlayer", animation: "walk"}
animation.stop {path: "Player/AnimationPlayer"}
animation.list {path: "Player/AnimationPlayer"}

Step 2: Implementation

addons/openclaw/tools.gd:

# Add to TOOLS array
var TOOLS = [
    # ... existing tools ...
    
    {
        "name": "animation.play",
        "description": "Play an animation on AnimationPlayer",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Path to AnimationPlayer node"},
                "animation": {"type": "string", "description": "Animation name to play"},
                "speed": {"type": "number", "description": "Playback speed (default: 1.0)"}
            },
            "required": ["path", "animation"]
        }
    },
    {
        "name": "animation.stop",
        "description": "Stop animation on AnimationPlayer",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Path to AnimationPlayer node"}
            },
            "required": ["path"]
        }
    },
    {
        "name": "animation.list",
        "description": "List available animations",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Path to AnimationPlayer node"}
            },
            "required": ["path"]
        }
    }
]

# Add handlers to execute function
func execute(tool_name: String, args: Dictionary) -> Dictionary:
    match tool_name:
        # ... existing cases ...
        "animation.play": return _animation_play(args)
        "animation.stop": return _animation_stop(args)
        "animation.list": return _animation_list(args)

# Implementation
func _animation_play(args: Dictionary) -> Dictionary:
    var path = args.get("path", "")
    var animation = args.get("animation", "")
    var speed = args.get("speed", 1.0)
    
    var node = _find_node_by_path(path)
    if node == null:
        return {"success": false, "error": "Node not found: " + path}
    
    if not node is AnimationPlayer:
        return {"success": false, "error": "Node is not AnimationPlayer: " + path}
    
    var player = node as AnimationPlayer
    
    if not player.has_animation(animation):
        return {"success": false, "error": "Animation not found: " + animation}
    
    player.speed_scale = speed
    player.play(animation)
    
    return {
        "success": true,
        "animation": animation,
        "duration": player.get_animation(animation).length,
        "speed": speed
    }

func _animation_stop(args: Dictionary) -> Dictionary:
    var path = args.get("path", "")
    
    var node = _find_node_by_path(path)
    if node == null:
        return {"success": false, "error": "Node not found: " + path}
    
    if not node is AnimationPlayer:
        return {"success": false, "error": "Node is not AnimationPlayer"}
    
    (node as AnimationPlayer).stop()
    
    return {"success": true}

func _animation_list(args: Dictionary) -> Dictionary:
    var path = args.get("path", "")
    
    var node = _find_node_by_path(path)
    if node == null:
        return {"success": false, "error": "Node not found: " + path}
    
    if not node is AnimationPlayer:
        return {"success": false, "error": "Node is not AnimationPlayer"}
    
    var player = node as AnimationPlayer
    var animations = []
    
    for anim_name in player.get_animation_list():
        var anim = player.get_animation(anim_name)
        animations.append({
            "name": anim_name,
            "duration": anim.length,
            "loop": anim.loop_mode != Animation.LOOP_NONE
        })
    
    return {
        "success": true,
        "animations": animations,
        "current": player.current_animation
    }

Step 3: Write Tests

# Add to test_tools.gd
func _test_animation_tools():
    _section("Animation Tools")
    
    # Prepare test scene
    tools.execute("scene.create", {"rootType": "Node2D", "name": "AnimTest"})
    
    # Test animation.list
    var list = tools.execute("animation.list", {
        "path": "AnimationPlayer"  # Use actual path
    })
    # _assert(list.success, "animation.list works")

Step 4: Update Documentation

Add to README.md:

### Animation Tools (3) - NEW
| Tool | Description |
|------|-------------|
| `animation.play` | Play animation on AnimationPlayer |
| `animation.stop` | Stop current animation |
| `animation.list` | List available animations |

Example 2: Bug Fix (Connection Timeout)

Issue Analysis

## Bug: Connection timeout after 60 seconds of inactivity

### Steps to Reproduce
1. Enable plugin
2. Wait 60 seconds with no commands
3. Execute command - timeout occurs

### Expected Behavior
Connection should be maintained

### Environment
- Godot 4.6
- OpenClaw 2026.2.3

Fix

connection_manager.gd:

# Problem: heartbeat interval too long
const HEARTBEAT_INTERVAL = 60.0  # 60 seconds (too long)

# Fix: reduce to 30 seconds
const HEARTBEAT_INTERVAL = 30.0  # 30 seconds

# Add: improved reconnection logic
func _on_heartbeat_timeout():
    if not is_connected_flag:
        # Attempt reconnection
        _reconnect()
        return
    
    _send_heartbeat()

func _reconnect():
    print("[OpenClaw] Attempting to reconnect...")
    _register()

Test

# Wait 70 seconds then execute command
sleep 70
openclaw godot execute editor.getState
# Should succeed

Pull Request Guidelines

PR Template

## Changes

<!-- Describe what was changed -->
Added 3 new tools for AnimationPlayer control

## Related Issue

Fixes #42

## Change Type

- [x] New feature
- [ ] Bug fix
- [ ] Documentation
- [ ] Refactoring

## Testing

- [x] Tested on Godot 4.6
- [x] Existing tools work correctly
- [x] New tools work correctly

## Checklist

- [x] Code style followed
- [x] Documentation updated
- [x] Tests added/updated
- [ ] No breaking changes

## Screenshots (if applicable)

<!-- Attach screenshots for UI changes -->

Good Commit Messages

# Feature addition
feat(tools): add animation.play, animation.stop, animation.list tools

# Bug fix
fix(connection): reduce heartbeat interval to 30s to prevent timeout

# Documentation
docs(readme): add animation tools to feature list

# Refactoring
refactor(tools): extract common node finding logic

# Style/formatting
style(tools): apply consistent indentation

Conventional Commits Format

<type>(<scope>): <subject>

[optional body]

[optional footer]

Types: feat, fix, docs, style, refactor, test, chore


Issue Reporting

Bug Report

## Bug Description

<!-- Clearly describe what went wrong -->

## Steps to Reproduce

1. Navigate to '...'
2. Click '...'
3. Scroll '...'
4. Error occurs

## Expected Behavior

<!-- Describe what should happen -->

## Actual Behavior

<!-- Describe what actually happens -->

## Environment

- OS: macOS 15.3
- Godot: 4.6-stable
- OpenClaw: 2026.2.3
- Plugin version: 1.1.0

## Logs

[OpenClaw] Error: ...


## Screenshots

<!-- Attach if applicable -->

Feature Request

## Feature Description

<!-- Describe the desired feature -->

## Use Cases

<!-- Why is this feature needed -->

## Proposed API

<!-- Suggest API format if possible -->
```gdscript
animation.play {path: "...", animation: "walk"}

Alternatives


---

## Code Review Process

### Reviewer Checklist

- [ ] Does the code work for its intended purpose?
- [ ] Are tests sufficient?
- [ ] Is documentation updated?
- [ ] Is code style consistent?
- [ ] Is error handling appropriate?
- [ ] Are there performance concerns?
- [ ] Are there security concerns?

### Review Comment Examples

```markdown
# Positive
✅ LGTM! Clean implementation.

# Suggestion
💡 Using `is_instance_of()` instead of `is` would be safer here.

# Request
⚠️ Null check needed for this case.

# Question
❓ What's the rationale for this constant value?

Community Guidelines

Code of Conduct

  • 🤝 Communicate respectfully
  • 🌍 Respect diverse backgrounds and experiences
  • 📚 Provide constructive feedback
  • 🚫 No discrimination or harassment

Communication Channels

  • GitHub Issues: Bugs, feature requests
  • GitHub Discussions: Questions, ideas
  • Discord: OpenClaw Community

Response Times

  • Issues: 1-3 days
  • PRs: Within 1 week for initial review
  • Critical bugs: ASAP

License

Contributed code is distributed under the MIT License.


Thank You! 🙏

Every contribution helps. Even small typo fixes are valuable!

If you have questions, please open an issue.


Documentation Updated: 2026-02-08