Files
Code_of_Conquest/godot_client/docs/ARCHITECTURE.md
2025-11-24 23:10:55 -06:00

768 lines
22 KiB
Markdown

# Godot Client Architecture
## Overview
The Godot client is a native frontend for Code of Conquest that connects to the Flask backend via REST API. It provides a consistent experience across desktop, mobile, and web platforms.
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ Godot Client │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ UI Layer (Scenes) │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Auth │ │ Char │ │ Combat │ │ World │ │ │
│ │ │ Screens│ │ Mgmt │ │ UI │ │ UI │ │ │
│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Component Layer │ │
│ │ ┌─────────┐ ┌──────┐ ┌──────────┐ ┌──────┐ │ │
│ │ │ Buttons │ │ Cards│ │FormFields│ │ ... │ │ │
│ │ └─────────┘ └──────┘ └──────────┘ └──────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Service Layer (Singletons) │ │
│ │ ┌──────────────┐ ┌────────────────┐ │ │
│ │ │ HTTPClient │ │ StateManager │ │ │
│ │ │ - API calls │ │ - Session │ │ │
│ │ │ - Auth token │ │ - Characters │ │ │
│ │ │ - Error │ │ - Wizard state │ │ │
│ │ │ handling │ │ - Persistence │ │ │
│ │ └──────────────┘ └────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Data Models (GDScript) │ │
│ │ Character, CharacterClass, Origin, Skill, etc. │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
HTTP/JSON REST API
┌─────────────────────────────────────────────────────────────┐
│ Flask Backend │
│ (Existing REST API) │
│ /api/v1/auth/*, /api/v1/characters/* │
└─────────────────────────────────────────────────────────────┘
```
## Layers
### 1. UI Layer (Scenes)
**Location**: `scenes/`
Individual screens and pages of the application.
**Responsibilities**:
- Render UI elements
- Handle user input
- Display data from StateManager
- Trigger API calls via HTTPClient
- Navigate between scenes
**Examples**:
- `scenes/auth/login.tscn` - Login screen
- `scenes/character/character_list.tscn` - Character list
- `scenes/character/create_wizard_step1.tscn` - Character creation step 1
**Communication**:
- Uses components from Component Layer
- Reads/writes to StateManager
- Calls HTTPClient for API requests
- Emits signals for navigation
### 2. Component Layer
**Location**: `scenes/components/`, `scripts/components/`
Reusable UI components shared across scenes.
**Responsibilities**:
- Provide consistent UI elements
- Encapsulate common patterns
- Handle component-specific logic
- Emit signals for parent scenes
**Examples**:
- `CustomButton` - Themed button with variants
- `Card` - Container with header/footer
- `FormField` - Input field with validation
**Communication**:
- Receives props from parent scenes
- Emits signals to parent scenes
- Uses ThemeColors for styling
- Independent of business logic
### 3. Service Layer (Singletons)
**Location**: `scripts/services/`
Global services accessible from anywhere in the app.
**Responsibilities**:
- Manage API communication
- Store global state
- Handle persistence
- Provide utilities
**Singletons**:
#### HTTPClient
- Base URL configuration
- Authentication token management
- JSON request/response handling
- Error handling and retries
- Timeout management
**API**:
```gdscript
HTTPClient.http_get(endpoint, callback, error_callback)
HTTPClient.http_post(endpoint, data, callback, error_callback)
HTTPClient.http_put(endpoint, data, callback, error_callback)
HTTPClient.http_delete(endpoint, callback, error_callback)
HTTPClient.http_patch(endpoint, data, callback, error_callback)
HTTPClient.set_auth_token(token)
```
#### StateManager
- User session state
- Character data cache
- Wizard state (character creation)
- Navigation history
- Settings and preferences
- Save/load to local storage
**API**:
```gdscript
StateManager.set_user_session(user_data, token)
StateManager.get_characters()
StateManager.set_wizard_origin(origin_data)
StateManager.save_state()
```
**Communication**:
- HTTPClient → Backend API (HTTP)
- StateManager → Local storage (FileAccess)
- Services → UI via signals
**Accessing Autoloads**:
Autoload singletons can be accessed in three ways:
1. **Direct access** (simple, works in `_ready()` and later):
```gdscript
func _ready():
HTTPClient.http_get("/api/v1/characters", _on_success)
```
2. **Get node explicitly** (verbose but always works):
```gdscript
func _on_button_clicked():
get_node("/root/HTTPClient").http_get("/api/v1/characters", _on_success)
```
3. **Cached reference with @onready** (best for multiple uses):
```gdscript
@onready var http_client = get_node("/root/HTTPClient")
@onready var state_manager = get_node("/root/StateManager")
func _ready():
http_client.http_get("/api/v1/characters", _on_success)
```
**Important**: When one autoload needs to reference another (like StateManager calling HTTPClient), use `@onready` to avoid script parsing errors. Direct access (`HTTPClient.method()`) only works during runtime, not during script compilation.
### 4. Data Models
**Location**: `scripts/models/`
GDScript classes that mirror backend data structures.
**Responsibilities**:
- Define typed data structures
- Validate data
- Serialize/deserialize JSON
- Provide helper methods
**Examples**:
```gdscript
class_name Character
var id: String
var name: String
var origin_id: String
var class_id: String
var level: int
var hp: int
var max_hp: int
var gold: int
var skills: Array[Skill]
static func from_json(data: Dictionary) -> Character:
# Parse JSON to Character object
pass
```
**Communication**:
- Created from API responses
- Stored in StateManager
- Displayed in UI scenes
## Data Flow
### Example: User Login
```
1. User enters credentials in LoginScene
2. LoginScene calls HTTPClient.http_post("/api/v1/auth/login", {...})
3. HTTPClient sends HTTP request to Flask backend
4. Backend validates and returns JWT token + user data
5. HTTPClient receives response, creates APIResponse object
6. LoginScene receives APIResponse via callback
7. LoginScene calls StateManager.set_user_session(user_data, token)
8. StateManager stores session and emits user_logged_in signal
9. StateManager saves state to local storage
10. LoginScene navigates to CharacterListScene
```
### Example: Load Characters
```
1. CharacterListScene calls HTTPClient.http_get("/api/v1/characters")
2. HTTPClient includes auth token in headers
3. Backend returns array of character data
4. CharacterListScene receives response
5. CharacterListScene calls StateManager.set_characters(characters)
6. StateManager emits characters_updated signal
7. CharacterListScene updates UI with character cards
```
### Example: Character Creation Wizard
```
1. User selects origin in WizardStep1Scene
2. Scene calls StateManager.set_wizard_origin(origin_data)
3. Scene navigates to WizardStep2Scene
4. User selects class in WizardStep2Scene
5. Scene calls StateManager.set_wizard_class(class_data)
6. User continues through steps 3 and 4
7. WizardStep4Scene (confirmation) calls HTTPClient.http_post(
"/api/v1/characters",
{
"origin_id": StateManager.get_wizard_origin().id,
"class_id": StateManager.get_wizard_class().id,
"name": StateManager.get_wizard_name()
}
)
8. Backend creates character and returns character data
9. Scene calls StateManager.add_character(character_data)
10. Scene calls StateManager.reset_wizard()
11. Scene navigates back to CharacterListScene
```
## Navigation
### Scene Management
Godot's built-in scene tree is used for navigation:
```gdscript
# Navigate to another scene
get_tree().change_scene_to_file("res://scenes/character/character_list.tscn")
# Or use PackedScene for preloading
var next_scene = preload("res://scenes/auth/login.tscn")
get_tree().change_scene_to_packed(next_scene)
```
### Navigation Flow
```
App Start
Main Scene (checks auth)
├─ Authenticated? → CharacterListScene
└─ Not authenticated? → LoginScene
Login successful → CharacterListScene
Create Character → WizardStep1Scene
Step 2 → WizardStep2Scene
Step 3 → WizardStep3Scene
Step 4 → WizardStep4Scene
Complete → CharacterListScene
Select Character → CharacterDetailScene
Start Game → GameWorldScene
Enter Combat → CombatScene
```
### Navigation Helper
Consider creating a navigation manager:
```gdscript
# scripts/services/navigation_manager.gd
extends Node
const SCENE_LOGIN = "res://scenes/auth/login.tscn"
const SCENE_CHARACTER_LIST = "res://scenes/character/character_list.tscn"
const SCENE_WIZARD_STEP_1 = "res://scenes/character/create_wizard_step1.tscn"
# ... etc
func navigate_to(scene_path: String) -> void:
StateManager.set_current_scene(scene_path)
get_tree().change_scene_to_file(scene_path)
func navigate_to_login() -> void:
navigate_to(SCENE_LOGIN)
func navigate_to_character_list() -> void:
navigate_to(SCENE_CHARACTER_LIST)
# etc.
```
## Authentication Flow
### Initial Load
```gdscript
# main.gd (attached to main.tscn, first scene loaded)
extends Control
func _ready() -> void:
# StateManager auto-loads from local storage in its _ready()
# Check if user is authenticated
if StateManager.is_authenticated():
# Validate token by making a test API call
HTTPClient.http_get("/api/v1/auth/validate", _on_token_validated, _on_token_invalid)
else:
# Go to login
NavigationManager.navigate_to_login()
func _on_token_validated(response: HTTPClient.APIResponse) -> void:
if response.is_success():
# Token is valid, go to character list
NavigationManager.navigate_to_character_list()
else:
# Token expired, clear and go to login
StateManager.clear_user_session()
NavigationManager.navigate_to_login()
func _on_token_invalid(response: HTTPClient.APIResponse) -> void:
# Network error or token invalid
StateManager.clear_user_session()
NavigationManager.navigate_to_login()
```
### Login
```gdscript
# scenes/auth/login.gd
extends Control
@onready var email_field: FormField = $EmailField
@onready var password_field: FormField = $PasswordField
@onready var login_button: CustomButton = $LoginButton
@onready var error_label: Label = $ErrorLabel
func _on_login_button_clicked() -> void:
# Validate fields
if not email_field.validate() or not password_field.validate():
return
# Show loading state
login_button.set_loading(true)
error_label.visible = false
# Make API call
var credentials = {
"email": email_field.get_value(),
"password": password_field.get_value()
}
HTTPClient.http_post("/api/v1/auth/login", credentials, _on_login_success, _on_login_error)
func _on_login_success(response: APIResponse) -> void:
login_button.set_loading(false)
if response.is_success():
# Extract user data and token from response
var user_data = response.result.get("user", {})
var token = response.result.get("token", "")
# Store in StateManager
StateManager.set_user_session(user_data, token)
# Navigate to character list
NavigationManager.navigate_to_character_list()
else:
# Show error
error_label.text = response.get_error_message()
error_label.visible = true
func _on_login_error(response: APIResponse) -> void:
login_button.set_loading(false)
error_label.text = "Failed to connect. Please try again."
error_label.visible = true
```
### Logout
```gdscript
func _on_logout_button_clicked() -> void:
# Call logout endpoint (optional, for server-side cleanup)
HTTPClient.http_post("/api/v1/auth/logout", {}, _on_logout_complete)
func _on_logout_complete(response: APIResponse) -> void:
# Clear local session regardless of API response
StateManager.clear_user_session()
# Navigate to login
NavigationManager.navigate_to_login()
```
## State Persistence
### What Gets Saved
StateManager saves to `user://coc_state.save` (platform-specific location):
- User session data
- Auth token (if "remember me")
- Character list cache
- Selected character ID
- Character limits
- Settings/preferences
- Timestamp of last save
### Save Triggers
State is saved automatically:
- On login
- On logout (clears save)
- On character list update
- On character selection
- On settings change
- On app exit (if auto-save enabled)
Manual save:
```gdscript
StateManager.save_state()
```
### Load on Startup
StateManager automatically loads on `_ready()`:
- Restores user session if valid
- Restores character list
- Sets HTTPClient auth token
## Error Handling
### API Errors
```gdscript
HTTPClient.http_get("/api/v1/characters", _on_success, _on_error)
func _on_success(response: APIResponse) -> void:
if response.is_success():
# Handle success
var characters = response.result
else:
# API returned error (4xx, 5xx)
_show_error(response.get_error_message())
func _on_error(response: APIResponse) -> void:
# Network error or JSON parse error
_show_error("Failed to connect to server")
```
### Network Errors
HTTPClient handles:
- Connection timeout (30s default)
- DNS resolution failure
- TLS/SSL errors
- No response from server
- Invalid JSON responses
All errors return an `APIResponse` with:
- `status`: HTTP status code (or 500 for network errors)
- `error.message`: Human-readable error message
- `error.code`: Machine-readable error code
### User-Facing Errors
Show errors to users:
```gdscript
# Toast notification (create a notification system)
NotificationManager.show_error("Failed to load characters")
# Inline error label
error_label.text = response.get_error_message()
error_label.visible = true
# Error dialog
var dialog = AcceptDialog.new()
dialog.dialog_text = "An error occurred. Please try again."
dialog.popup_centered()
```
## Platform Considerations
### Mobile
**Touch Input**:
- Larger tap targets (minimum 44x44 dp)
- No hover states
- Swipe gestures for navigation
**Screen Sizes**:
- Responsive layouts
- Safe areas (notches, rounded corners)
- Portrait and landscape orientations
**Performance**:
- Limit draw calls
- Use compressed textures
- Reduce particle effects
**Networking**:
- Handle intermittent connectivity
- Show loading indicators
- Cache data locally
### Desktop
**Input**:
- Keyboard shortcuts
- Mouse hover effects
- Scroll wheel support
**Window Management**:
- Resizable windows
- Fullscreen mode
- Multiple monitor support
### Web
**Limitations**:
- No threading (slower)
- No file system access (use IndexedDB)
- Larger download size
- CORS restrictions
**Requirements**:
- HTTPS for SharedArrayBuffer
- Proper MIME types
- COOP/COEP headers
## Security Considerations
### Token Storage
**Desktop/Mobile**: Tokens stored in encrypted local storage
**Web**: Tokens stored in localStorage (less secure, consider sessionStorage)
### HTTPS Only
Always use HTTPS in production for API calls.
### Token Expiration
Handle expired tokens:
```gdscript
func _on_api_error(response: HTTPClient.APIResponse) -> void:
if response.status == 401: # Unauthorized
# Token expired, logout
StateManager.clear_user_session()
NavigationManager.navigate_to_login()
```
### Input Validation
Always validate user input:
- FormField components have built-in validation
- Re-validate on backend (never trust client)
- Sanitize before displaying (prevent XSS if user-generated content)
## Testing Strategy
### Unit Tests
Test individual components:
- HTTPClient request/response handling
- StateManager save/load
- Data model serialization
- Component validation logic
### Integration Tests
Test scene flows:
- Login → Character List
- Character Creation wizard
- Character selection → Game
### Platform Tests
Test on each platform:
- Desktop: Windows, Mac, Linux
- Mobile: Android, iOS
- Web: Chrome, Firefox, Safari
### API Mocking
For offline testing, create mock responses:
```gdscript
# scripts/services/mock_http_client.gd
extends Node
# Same API as HTTPClient but returns mock data
func get(endpoint: String, callback: Callable, error_callback: Callable = Callable()) -> void:
match endpoint:
"/api/v1/characters":
callback.call(_mock_character_list_response())
_:
error_callback.call(_mock_error_response())
func _mock_character_list_response() -> HTTPClient.APIResponse:
var mock_data = {
"app": "Code of Conquest",
"version": "0.1.0",
"status": 200,
"timestamp": Time.get_datetime_string_from_system(),
"result": [
{"id": "1", "name": "Warrior", "level": 5, "hp": 100, "max_hp": 100},
{"id": "2", "name": "Mage", "level": 3, "hp": 60, "max_hp": 60}
],
"error": {}
}
return HTTPClient.APIResponse.new(mock_data)
```
## Performance Optimization
### Minimize API Calls
- Cache data in StateManager
- Only refetch when necessary
- Use pagination for large lists
### Optimize Rendering
- Use object pooling for lists
- Minimize StyleBox allocations
- Use TextureRect instead of Sprite when possible
- Reduce shadow/glow effects on mobile
### Lazy Loading
Load scenes only when needed:
```gdscript
# Preload for instant transition
const CharacterList = preload("res://scenes/character/character_list.tscn")
# Or load on-demand
var scene = load("res://scenes/character/character_list.tscn")
```
## Debugging
### Enable Logging
```gdscript
# Add to HTTPClient, StateManager, etc.
print("[HTTPClient] GET /api/v1/characters")
print("[StateManager] User logged in: ", user_data.get("email"))
```
### Remote Debugging
For mobile:
1. Enable remote debug in Godot
2. Connect device via USB
3. Run with "Remote Debug" option
### Network Inspector
Monitor network traffic:
- Desktop: Use browser dev tools for Godot Web export
- Mobile: Use Charles Proxy or similar
- Check request/response bodies, headers, timing
## Next Steps (Phase 2)
After Phase 1 (Foundation) is complete:
1. **Implement Auth Screens** (Week 2)
- Login, Register, Password Reset
- Email verification
- Form validation
2. **Implement Character Management** (Weeks 3-4)
- Character creation wizard
- Character list
- Character detail
- Delete character
3. **Platform Optimization** (Week 5)
- Mobile touch controls
- Desktop keyboard shortcuts
- Web export configuration
4. **Future Features** (Week 6+)
- Combat UI
- World exploration
- Quest system
- Multiplayer
## Resources
- [Godot Documentation](https://docs.godotengine.org/)
- [GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html)
- [Godot UI Tutorials](https://docs.godotengine.org/en/stable/tutorials/ui/index.html)
- [HTTP Request](https://docs.godotengine.org/en/stable/classes/class_httprequest.html)
- [FileAccess](https://docs.godotengine.org/en/stable/classes/class_fileaccess.html)