# 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)