Files
ai-game-2/AGENTS.md
2026-03-09 09:22:21 -07:00

298 lines
8.6 KiB
Markdown

# AGENTS.md - AI Agent Guidelines for ai-game-2
## Project Overview
This is a **Godot 4.6** game project (King's Quest IV remake). The project uses:
- Godot's scene system (`.tscn` files)
- GDScript (`.gd` files) for game logic
- NavigationServer2D for pathfinding
- Custom script builder pattern for game scripts
## Tools
* Helper scripts (python) exist in ./tools
* a virtual env exists there, too. When you need to run one, you should source ./tools/venv/bin/activate
## Build & Development Commands
### Running the Game
```bash
# Open in Godot editor (headless for CI)
godot --headless --path .
# Or run the exported game (if built)
./export/linux/ai-game
```
### Single Test/Scene Testing
Godot doesn't have a traditional test framework. To test a single scene:
1. Open the project in Godot
2. Set the scene as the main scene in project.godot
3. Run with F5
### Code Quality
```bash
# Godot's built-in lint (via editor)
# Tools > Analyze > Check Code
# For CI, you can run headless:
godot --headless --script-check .
```
## Code Style Guidelines
*** CRITICAL ****
When you need to create a new uid, run python make_uid.py, and it will return a unique uid. When creating scenes, at a <scene>.tscn.uid with the contents uid://<uid>
### File Organization
- **Scenes**: `scenes/kq4_XXX_room_name/`
- **Scripts**: `scenes/kq4_XXX_room_name/kq4_XXX_room_name.gd`
- **Resources**: Visual assets copied to room folders
- **Template**: Use `scenes/kq4_placeholder_template/` when creating new rooms
### Naming Conventions
| Element | Convention | Example |
|---------|------------|---------|
| Scenes | `kq4_XXX_description` (snake_case) | `kq4_010_forest_path` |
| Nodes | PascalCase | `kq4_004_ogres_cottage` |
| Scripts | snake_case.gd | `kq4_010_forest_path.gd` |
| Variables | snake_case | `appear_at_node` |
| Constants | SCREAMING_SNAKE | `const MAX_SPEED = 100` |
| Classes | PascalCase | `class_name Scene` |
### GDScript Patterns
#### Class Definition
```gdscript
extends Node2D
class_name MyClass
# Use @export for editor-exposed variables
@export var my_var: int = 0
@export_file("*.tscn") var target_scene: String
```
#### Type Hints (Required)
```gdscript
var player: Node2D
var score: int = 0
var name: String = ""
func move_to(position: Vector2) -> void:
pass
```
#### Signals
```gdscript
signal my_signal(value: int)
# Connecting
some_node.my_signal.connect(_on_my_signal)
# Or in editor-created connections:
[connection signal="interacted" from="transition_piece" to="." method="_on_interacted"]
```
### Scene Structure (.tscn)
#### Header
```gdscript
[gd_scene format=3 uid="uid://xxxxx"]
[ext_resource type="Script" uid="uid://xxxxx" path="res://path/to/script.gd" id="1_abc"]
[ext_resource type="Texture2D" uid="uid://xxxxx" path="res://path/to/texture.png" id="2_abc"]
[ext_resource type="PackedScene" uid="uid://xxxxx" path="res://TransitionPiece.tscn" id="3_abc"]
```
#### Node Hierarchy
- Use meaningful node names
- Prefix private nodes with underscore when appropriate
- Use `unique_id=` for editor-managed nodes
#### Transition Pieces
When adding exits to rooms:
```gdscript
[node name="kq4_004_ogres_cottage" parent="." instance=ExtResource("4_abc")]
position = Vector2(910, -213)
polygon = PackedVector2Array(...)
appear_at_node = "kq4_010_forest_path" # Node name in target scene
target = "uid://xxxxxxxx" # Scene UID
label = "Ogre's Cottage"
[node name="entrance" parent="kq4_004_ogres_cottage" index="0"]
position = Vector2(163, 598)
[node name="exit" parent="kq4_004_ogres_cottage" index="1"]
position = Vector2(159, 459)
[connection signal="interacted" from="kq4_004_ogres_cottage" to="." method="_on_ogres_cottage_interacted"]
[editable path="kq4_004_ogres_cottage"]
```
### Error Handling
- Use `push_error()` for critical issues
- Use `push_warning()` for non-critical issues
- Always check `load()` returns with `if resource:` before using
- Use `@onready` for node references (handles load order)
### Room Creation Process
1. **Copy template**: `cp -r scenes/kq4_placeholder_template scenes/kq4_XXX_name`
2. **Rename files**: Update `.gd` and `.tscn` filenames
3. **Copy visual**: Copy `pic_XXX_visual.png` from decompile
4. **Update .tscn**:
- Remove hardcoded UIDs (let Godot generate)
- Add transition pieces for cardinal directions
- Set labels to human-readable names
5. **Update .gd**: Add signal handlers for connected rooms
6. **Generate UIDs**: Let Godot import, then create `.uid` files
7. **Wire transitions**: Update `target` fields with real UIDs
### Import Files
When creating new room textures, create `.import` files:
```ini
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://xxxxxxxx"
path="res://.godot/imported/image.png-xxx.ctex"
[deps]
source_file="res://scenes/room_name/image.png"
dest_files=[...]
[params]
compress/mode=0
# ... standard Godot texture params
```
### What NOT to Do
- **Don't** commit secrets/keys (use `.gitignore`)
- **Don't** use `print()` for debugging in production (use `push_warning()`)
- **Don't** hardcode paths - use `res://` relative paths
- **Don't** modify `project.godot` unless necessary
- **Don't** delete the `meadow-2` scene (unrelated to KQ4 rooms)
### File Extensions to Know
| Extension | Purpose |
|-----------|---------|
| `.gd` | GDScript source |
| `.tscn` | Godot scene |
| `.tres` | Godot resource |
| `.import` | Import configuration |
| `.uid` | Resource UID cache |
| `.md` | Room documentation (in kq4-sierra-decompile) |
### Room Specs Reference
Room specifications are in `kq4-sierra-decompile/rooms/kq4-XXX-name/`.
Each room has:
- `kq4-XXX-name.md` - Technical documentation
- `pic_XXX_visual.png` - Background image
- `pic_XXX_control.png` - Control/hitbox areas
- `pic_XXX_priority.png` - Priority/z-ordering
### Key Classes
- `Scene` (Scene.gd) - Base room class with navigation
- `TransitionPiece` (TransitionPiece.gd) - Exit/entry points
- `GameScript` (GameScript.gd) - Script builder for cutscenes
- `Ego` (Ego.gd) - Player character
## Interaction System (Global)
The game uses a **cursor-based action system** with 4 modes that cycle via right-click:
| Cursor | Action | Icon | Purpose |
|--------|--------|------|---------|
| 0 | Walk | boot_icon.png | Move player to location |
| 1 | Look | eye_icon.png | Examine objects/room |
| 2 | Touch | hand_icon.png | Interact with objects |
| 3 | Talk | speech_icon.png | Talk to characters |
### Key Components
#### ActionState (Autoload)
Global singleton (`ActionState.gd`) that tracks the current cursor action:
```gdscript
ActionState.current_action # 0-3 (WALK, LOOK, TOUCH, TALK)
ActionState.Action.WALK # Enum values
```
#### SetPiece Nodes
Interactive areas in rooms use `SetPiece_.gd` (extends Polygon2D):
**Signals emitted:**
- `interacted` - Generic interaction (takes precedence over cursor actions)
- `walked` - When walk cursor clicks the piece
- `looked` - When look cursor clicks the piece
- `touched` - When touch cursor clicks the piece
- `talked` - When talk cursor clicks the piece
**Priority Rule:** If `interacted` signal has connections, it overrides cursor-specific actions.
### Room Setup for Interactions
1. **Add SetPiece nodes** to room scene for interactive objects:
```gdscript
[node name="pool" type="Polygon2D" parent="."]
polygon = PackedVector2Array(...)
script = ExtResource("SetPiece_.gd")
label = "Pool"
```
2. **Connect signals** in .tscn:
```gdscript
[connection signal="looked" from="pool" to="." method="_on_pool_looked"]
```
3. **Implement handlers** in room script:
```gdscript
func _on_pool_looked() -> void:
start_main_script(ScriptBuilder.init(
ScriptBuilder.say(ego, "The water looks inviting.")
).build(self, "_on_script_complete"))
```
### Room-Wide Look
Clicking empty space with look cursor triggers `_on_room_looked()`:
```gdscript
func _on_room_looked() -> void:
# Override in room script for room description
start_main_script(ScriptBuilder.init(
ScriptBuilder.say(ego, "Room description here...")
).build(self, "_on_script_complete"))
```
### Behavior Rules
1. **Precedence**: `interacted` signal always wins if connected
2. **Fallback**: If no handler for cursor action, do nothing (silent)
3. **SetPiece detection**: Mouse must be inside polygon
4. **Unhandled clicks**: Scene._unhandled_input handles empty space
### Common Patterns
#### Navigation
```gdscript
var path = NavigationServer2D.map_get_path(map, start, end, true)
```
#### Pathfinding Movement
```gdscript
scene.start_main_script(scene.ScriptBuilder.init(scene.ScriptBuilder.walk_path(ego, path)).build())
```
#### Scene Transitions
```gdscript
func _on_exit_interacted() -> void:
$target_scene.default_script(self)
```