first commit

This commit is contained in:
2025-11-24 23:10:55 -06:00
commit 8315fa51c9
279 changed files with 74600 additions and 0 deletions

View File

@@ -0,0 +1,364 @@
extends Node
## HTTPClient Service
##
## Singleton service for all HTTP communication with the Flask backend.
## Handles authentication, JSON parsing, error handling, and provides
## a convenient interface for making API requests.
##
## Usage:
## HTTPClient.http_get("/api/v1/characters", _on_characters_loaded)
## HTTPClient.http_post("/api/v1/auth/login", {"email": "...", "password": "..."}, _on_login_complete)
# API Configuration
const API_TIMEOUT := 30.0 # Seconds
# HTTP Methods
enum Method {
GET,
POST,
PUT,
DELETE,
PATCH
}
# Internal state
var _auth_token: String = ""
var _session_cookie: String = "" # Session cookie for authentication
#var _request_queue: Array[Dictionary] = []
var _active_requests: Dictionary = {} # request_id -> HTTPRequest node
func _ready() -> void:
print("[HTTPClient] Service initialized. API Base URL: %s" % Settings.get_api_url())
## Make a GET request
##
## @param endpoint: API endpoint (e.g., "/api/v1/characters")
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_get(endpoint: String, callback: Callable, error_callback: Callable = Callable()) -> void:
_make_request(Method.GET, endpoint, {}, callback, error_callback)
## Make a POST request
##
## @param endpoint: API endpoint
## @param data: Dictionary to send as JSON body
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_post(endpoint: String, data: Dictionary, callback: Callable, error_callback: Callable = Callable()) -> void:
_make_request(Method.POST, endpoint, data, callback, error_callback)
## Make a PUT request
##
## @param endpoint: API endpoint
## @param data: Dictionary to send as JSON body
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_put(endpoint: String, data: Dictionary, callback: Callable, error_callback: Callable = Callable()) -> void:
_make_request(Method.PUT, endpoint, data, callback, error_callback)
## Make a DELETE request
##
## @param endpoint: API endpoint
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_delete(endpoint: String, callback: Callable, error_callback: Callable = Callable()) -> void:
_make_request(Method.DELETE, endpoint, {}, callback, error_callback)
## Make a PATCH request
##
## @param endpoint: API endpoint
## @param data: Dictionary to send as JSON body
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_patch(endpoint: String, data: Dictionary, callback: Callable, error_callback: Callable = Callable()) -> void:
_make_request(Method.PATCH, endpoint, data, callback, error_callback)
## Set the authentication token for subsequent requests
##
## @param token: JWT token from login/registration
func set_auth_token(token: String) -> void:
_auth_token = token
print("[HTTPClient] Auth token set")
## Clear the authentication token (logout)
func clear_auth_token() -> void:
_auth_token = ""
print("[HTTPClient] Auth token cleared")
## Get current auth token
func get_auth_token() -> String:
return _auth_token
## Set the session cookie for subsequent requests
##
## @param cookie: Session cookie value (e.g., "coc_session=xxx")
func set_session_cookie(cookie: String) -> void:
_session_cookie = cookie
print("[HTTPClient] Session cookie set")
## Clear the session cookie (logout)
func clear_session_cookie() -> void:
_session_cookie = ""
print("[HTTPClient] Session cookie cleared")
## Get current session cookie
func get_session_cookie() -> String:
return _session_cookie
## Check if authenticated
func is_authenticated() -> bool:
return not _auth_token.is_empty() or not _session_cookie.is_empty()
## Internal: Make HTTP request
func _make_request(
method: Method,
endpoint: String,
data: Dictionary,
callback: Callable,
error_callback: Callable
) -> void:
# Create HTTPRequest node
var http_request := HTTPRequest.new()
add_child(http_request)
# Generate request ID for tracking
var request_id := "%s_%d" % [endpoint.get_file(), Time.get_ticks_msec()]
_active_requests[request_id] = http_request
# Build full URL
var url := Settings.get_api_url() + endpoint
# Build headers
var headers := _build_headers()
# Connect completion signal
http_request.request_completed.connect(
_on_request_completed.bind(request_id, callback, error_callback)
)
# Set timeout
http_request.timeout = API_TIMEOUT
# Make request based on method
var method_int := _method_to_int(method)
var body := ""
if method in [Method.POST, Method.PUT, Method.PATCH]:
body = JSON.stringify(data)
print("[HTTPClient] %s %s" % [_method_to_string(method), url])
if not body.is_empty():
print("[HTTPClient] Body: %s" % body)
var error := http_request.request(url, headers, method_int, body)
if error != OK:
push_error("[HTTPClient] Failed to initiate request: %s" % error)
_cleanup_request(request_id)
if error_callback.is_valid():
error_callback.call(_create_error_response("Failed to initiate request", 0))
## Internal: Build request headers
func _build_headers() -> PackedStringArray:
var headers := PackedStringArray([
"Content-Type: application/json",
"Accept: application/json"
])
# Add auth token if present (for JWT auth)
if not _auth_token.is_empty():
headers.append("Authorization: Bearer %s" % _auth_token)
# Add session cookie if present (for cookie-based auth)
if not _session_cookie.is_empty():
headers.append("Cookie: %s" % _session_cookie)
return headers
## Internal: Handle request completion
func _on_request_completed(
result: int,
response_code: int,
headers: PackedStringArray,
body: PackedByteArray,
request_id: String,
callback: Callable,
error_callback: Callable
) -> void:
print("[HTTPClient] Request completed: %s (status=%d)" % [request_id, response_code])
# Extract Set-Cookie header if present
_extract_session_cookie(headers)
# Parse response body
var body_string := body.get_string_from_utf8()
# Handle network errors
if result != HTTPRequest.RESULT_SUCCESS:
push_error("[HTTPClient] Network error: %s" % _result_to_string(result))
_cleanup_request(request_id)
if error_callback.is_valid():
error_callback.call(_create_error_response(_result_to_string(result), response_code))
return
# Parse JSON response
var json := JSON.new()
var parse_error := json.parse(body_string)
if parse_error != OK:
push_error("[HTTPClient] Failed to parse JSON: %s" % body_string)
_cleanup_request(request_id)
if error_callback.is_valid():
error_callback.call(_create_error_response("Failed to parse JSON response", response_code))
return
# Create APIResponse
var api_response: APIResponse = APIResponse.new(json.data)
api_response.raw_response = body_string
# Check for errors
if api_response.has_error():
print("[HTTPClient] API error: %s" % api_response.get_error_message())
if error_callback.is_valid():
error_callback.call(api_response)
elif callback.is_valid():
# Call regular callback even with error if no error callback
callback.call(api_response)
else:
print("[HTTPClient] Success: %s" % request_id)
if callback.is_valid():
callback.call(api_response)
_cleanup_request(request_id)
## Internal: Extract session cookie from response headers
func _extract_session_cookie(headers: PackedStringArray) -> void:
for header in headers:
# Look for Set-Cookie header
if header.begins_with("Set-Cookie:") or header.begins_with("set-cookie:"):
# Extract cookie value
var cookie_string := header.substr(11).strip_edges() # Remove "Set-Cookie:"
# Look for coc_session cookie
if cookie_string.begins_with("coc_session="):
# Extract just the cookie name=value part (before semicolon)
var cookie_parts := cookie_string.split(";")
if cookie_parts.size() > 0:
_session_cookie = cookie_parts[0].strip_edges()
print("[HTTPClient] Session cookie extracted: %s" % _session_cookie)
return
## Internal: Cleanup request resources
func _cleanup_request(request_id: String) -> void:
if _active_requests.has(request_id):
var http_request: HTTPRequest = _active_requests[request_id]
http_request.queue_free()
_active_requests.erase(request_id)
## Internal: Create error response
func _create_error_response(message: String, status_code: int) -> APIResponse:
var error_data := {
"app": "Code of Conquest",
"version": "0.1.0",
"status": status_code if status_code > 0 else 500,
"timestamp": Time.get_datetime_string_from_system(),
"result": null,
"error": {
"message": message,
"code": "NETWORK_ERROR"
},
"meta": {}
}
return APIResponse.new(error_data)
## Internal: Convert Method enum to HTTPClient constant
func _method_to_int(method: Method) -> int:
match method:
Method.GET:
return HTTPClient.METHOD_GET
Method.POST:
return HTTPClient.METHOD_POST
Method.PUT:
return HTTPClient.METHOD_PUT
Method.DELETE:
return HTTPClient.METHOD_DELETE
Method.PATCH:
return HTTPClient.METHOD_PATCH
_:
return HTTPClient.METHOD_GET
## Internal: Convert Method enum to string
func _method_to_string(method: Method) -> String:
match method:
Method.GET:
return "GET"
Method.POST:
return "POST"
Method.PUT:
return "PUT"
Method.DELETE:
return "DELETE"
Method.PATCH:
return "PATCH"
_:
return "UNKNOWN"
## Internal: Convert HTTPRequest result to string
func _result_to_string(result: int) -> String:
match result:
HTTPRequest.RESULT_SUCCESS:
return "Success"
HTTPRequest.RESULT_CHUNKED_BODY_SIZE_MISMATCH:
return "Chunked body size mismatch"
HTTPRequest.RESULT_CANT_CONNECT:
return "Can't connect to server"
HTTPRequest.RESULT_CANT_RESOLVE:
return "Can't resolve hostname"
HTTPRequest.RESULT_CONNECTION_ERROR:
return "Connection error"
HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR:
return "TLS handshake error"
HTTPRequest.RESULT_NO_RESPONSE:
return "No response from server"
HTTPRequest.RESULT_BODY_SIZE_LIMIT_EXCEEDED:
return "Body size limit exceeded"
HTTPRequest.RESULT_BODY_DECOMPRESS_FAILED:
return "Body decompression failed"
HTTPRequest.RESULT_REQUEST_FAILED:
return "Request failed"
HTTPRequest.RESULT_DOWNLOAD_FILE_CANT_OPEN:
return "Can't open download file"
HTTPRequest.RESULT_DOWNLOAD_FILE_WRITE_ERROR:
return "Download file write error"
HTTPRequest.RESULT_REDIRECT_LIMIT_REACHED:
return "Redirect limit reached"
HTTPRequest.RESULT_TIMEOUT:
return "Request timeout"
_:
return "Unknown error (%d)" % result

View File

@@ -0,0 +1 @@
uid://du1woo6w2kr3b

View File

@@ -0,0 +1,204 @@
extends Node
## Settings Service
##
## Singleton service for application configuration.
## Stores URLs, feature flags, and other runtime settings.
##
## Usage:
## Settings.get_api_url()
## Settings.get_web_url()
## Settings.set_environment("production")
# Environment types
enum Env {
DEVELOPMENT,
STAGING,
PRODUCTION
}
# Current environment
var _current_environment: int = Env.DEVELOPMENT
# URL Configuration
var _api_urls := {
Env.DEVELOPMENT: "http://localhost:5000",
Env.STAGING: "https://staging-api.codeofconquest.com",
Env.PRODUCTION: "https://api.codeofconquest.com"
}
var _web_urls := {
Env.DEVELOPMENT: "http://localhost:8000", # Flask serves web pages in dev
Env.STAGING: "https://staging.codeofconquest.com",
Env.PRODUCTION: "https://www.codeofconquest.com"
}
# Feature flags
var _features := {
"enable_debug_logging": true,
"enable_analytics": false,
"enable_multiplayer": false,
"max_characters_per_user": 5
}
# User preferences (persisted)
var _preferences := {
"remember_login": true,
"auto_save": true,
"sound_enabled": true,
"music_enabled": true,
"sound_volume": 0.8,
"music_volume": 0.6
}
# Save file configuration
const SETTINGS_FILE_PATH := "user://settings.save"
const SETTINGS_VERSION := 1
func _ready() -> void:
_load_settings()
_detect_environment()
print("[Settings] Service initialized")
print("[Settings] Environment: %s" % _environment_to_string(_current_environment))
print("[Settings] API URL: %s" % get_api_url())
print("[Settings] Web URL: %s" % get_web_url())
## Get current API base URL
func get_api_url() -> String:
return _api_urls.get(_current_environment, _api_urls[Env.DEVELOPMENT])
## Get current web frontend base URL
func get_web_url() -> String:
return _web_urls.get(_current_environment, _web_urls[Env.DEVELOPMENT])
## Get current environment
func get_environment() -> int:
return _current_environment
## Set environment (for testing/switching)
func set_environment(env: int) -> void:
_current_environment = env
print("[Settings] Environment changed to: %s" % _environment_to_string(env))
print("[Settings] API URL: %s" % get_api_url())
print("[Settings] Web URL: %s" % get_web_url())
## Check if a feature is enabled
func is_feature_enabled(feature_name: String) -> bool:
return _features.get(feature_name, false)
## Get feature value
func get_feature(feature_name: String, default: Variant = null) -> Variant:
return _features.get(feature_name, default)
## Set feature flag (for testing/debugging)
func set_feature(feature_name: String, value: Variant) -> void:
_features[feature_name] = value
print("[Settings] Feature updated: %s = %s" % [feature_name, value])
## Get user preference
func get_preference(key: String, default: Variant = null) -> Variant:
return _preferences.get(key, default)
## Set user preference
func set_preference(key: String, value: Variant) -> void:
_preferences[key] = value
print("[Settings] Preference updated: %s = %s" % [key, value])
_save_settings()
## Get all preferences
func get_all_preferences() -> Dictionary:
return _preferences.duplicate()
## Auto-detect environment based on OS and build flags
func _detect_environment() -> void:
# Check for --production command line argument
var args := OS.get_cmdline_args()
if "--production" in args:
_current_environment = Env.PRODUCTION
return
if "--staging" in args:
_current_environment = Env.STAGING
return
# Default to development
_current_environment = Env.DEVELOPMENT
## Save settings to disk
func _save_settings() -> void:
var save_data := {
"version": SETTINGS_VERSION,
"preferences": _preferences,
"timestamp": Time.get_unix_time_from_system()
}
var file := FileAccess.open(SETTINGS_FILE_PATH, FileAccess.WRITE)
if file == null:
push_error("[Settings] Failed to open settings file for writing: %s" % FileAccess.get_open_error())
return
file.store_string(JSON.stringify(save_data))
file.close()
print("[Settings] Settings saved to %s" % SETTINGS_FILE_PATH)
## Load settings from disk
func _load_settings() -> void:
if not FileAccess.file_exists(SETTINGS_FILE_PATH):
print("[Settings] No settings file found, using defaults")
return
var file := FileAccess.open(SETTINGS_FILE_PATH, FileAccess.READ)
if file == null:
push_error("[Settings] Failed to open settings file for reading: %s" % FileAccess.get_open_error())
return
var json_string := file.get_as_text()
file.close()
var json := JSON.new()
var parse_error := json.parse(json_string)
if parse_error != OK:
push_error("[Settings] Failed to parse settings file")
return
var save_data: Dictionary = json.data
# Check version
if save_data.get("version", 0) != SETTINGS_VERSION:
print("[Settings] Settings file version mismatch, using defaults")
return
# Restore preferences
var saved_prefs = save_data.get("preferences", {})
for key in saved_prefs:
_preferences[key] = saved_prefs[key]
print("[Settings] Settings loaded from %s" % SETTINGS_FILE_PATH)
## Convert environment enum to string
func _environment_to_string(env: int) -> String:
match env:
Env.DEVELOPMENT:
return "Development"
Env.STAGING:
return "Staging"
Env.PRODUCTION:
return "Production"
_:
return "Unknown"

View File

@@ -0,0 +1 @@
uid://3s8i3b6v5mde

View File

@@ -0,0 +1,422 @@
extends Node
## StateManager Service
##
## Singleton service for managing global application state.
## Handles user session, character data, wizard state, navigation, and persistence.
##
## Usage:
## StateManager.set_user_session(user_data)
## var user = StateManager.get_current_user()
## StateManager.save_state() # Persist to local storage
# Signals for state changes
signal user_logged_in(user_data: Dictionary)
signal user_logged_out()
signal character_selected(character_id: String)
signal character_created(character_data: Dictionary)
signal character_deleted(character_id: String)
signal characters_updated(characters: Array)
# Save file configuration
const SAVE_FILE_PATH := "user://coc_state.save"
const SAVE_VERSION := 1
# User session state
var _current_user: Dictionary = {}
var _auth_token: String = ""
var _is_authenticated: bool = false
# Character state
var _characters: Array[Dictionary] = []
var _selected_character_id: String = ""
var _character_limits: Dictionary = {}
# Character creation wizard state
var _wizard_state: Dictionary = {
"step": 0, # Current step (0-3)
"selected_origin": null, # Selected origin data
"selected_class": null, # Selected class data
"character_name": "",
"character_customization": {}
}
# Navigation state
var _current_scene: String = ""
var _scene_history: Array[String] = []
# Settings (deprecated - use Settings service for preferences)
# Kept for backward compatibility with save files
var _settings: Dictionary = {
"remember_login": true,
"auto_save": true
}
# Reference to HTTPClient singleton (available after _ready)
@onready var http_client: Node = get_node("/root/HTTPClient")
func _ready() -> void:
print("[StateManager] Service initialized")
_load_state()
## Set user session data (after login/registration)
func set_user_session(user_data: Dictionary, token: String = "") -> void:
_current_user = user_data
_auth_token = token
_is_authenticated = true
# Update HTTPClient with token (if using JWT auth)
if not token.is_empty():
http_client.set_auth_token(token)
print("[StateManager] User session set: %s" % user_data.get("email", "unknown"))
# Emit signal
user_logged_in.emit(user_data)
# Auto-save if enabled
if _settings.get("auto_save", true):
save_state()
## Clear user session (logout)
func clear_user_session() -> void:
_current_user = {}
_auth_token = ""
_is_authenticated = false
_characters = []
_selected_character_id = ""
# Clear HTTPClient token and cookie
http_client.clear_auth_token()
http_client.clear_session_cookie()
print("[StateManager] User session cleared")
# Emit signal
user_logged_out.emit()
# Clear saved state
_clear_save_file()
## Get current user data
func get_current_user() -> Dictionary:
return _current_user
## Get current auth token
func get_auth_token() -> String:
return _auth_token
## Check if user is authenticated
func is_authenticated() -> bool:
# Check both token and cookie-based auth
return _is_authenticated and (not _auth_token.is_empty() or http_client.is_authenticated())
## Set characters list
func set_characters(characters: Array) -> void:
_characters.clear()
for character in characters:
_characters.append(character)
print("[StateManager] Characters updated: %d total" % _characters.size())
# Emit signal
characters_updated.emit(_characters)
# Auto-save if enabled
if _settings.get("auto_save", true):
save_state()
## Get all characters
func get_characters() -> Array[Dictionary]:
return _characters
## Add character to list
func add_character(character_data: Dictionary) -> void:
_characters.append(character_data)
print("[StateManager] Character added: %s" % character_data.get("name", "unknown"))
# Emit signal
character_created.emit(character_data)
# Auto-save if enabled
if _settings.get("auto_save", true):
save_state()
## Remove character from list
func remove_character(character_id: String) -> void:
for i in range(_characters.size()):
if _characters[i].get("id") == character_id:
_characters.remove_at(i)
print("[StateManager] Character removed: %s" % character_id)
# Clear selection if this was selected
if _selected_character_id == character_id:
_selected_character_id = ""
# Emit signal
character_deleted.emit(character_id)
# Auto-save if enabled
if _settings.get("auto_save", true):
save_state()
return
## Get character by ID
func get_character(character_id: String) -> Dictionary:
for character in _characters:
if character.get("id") == character_id:
return character
return {}
## Set selected character
func select_character(character_id: String) -> void:
_selected_character_id = character_id
print("[StateManager] Character selected: %s" % character_id)
# Emit signal
character_selected.emit(character_id)
## Get selected character
func get_selected_character() -> Dictionary:
return get_character(_selected_character_id)
## Get selected character ID
func get_selected_character_id() -> String:
return _selected_character_id
## Set character limits
func set_character_limits(limits: Dictionary) -> void:
_character_limits = limits
## Get character limits
func get_character_limits() -> Dictionary:
return _character_limits
## Character Creation Wizard State Management
## Reset wizard state
func reset_wizard() -> void:
_wizard_state = {
"step": 0,
"selected_origin": null,
"selected_class": null,
"character_name": "",
"character_customization": {}
}
print("[StateManager] Wizard state reset")
## Set wizard step
func set_wizard_step(step: int) -> void:
_wizard_state["step"] = step
## Get current wizard step
func get_wizard_step() -> int:
return _wizard_state.get("step", 0)
## Set selected origin
func set_wizard_origin(origin_data: Dictionary) -> void:
_wizard_state["selected_origin"] = origin_data
## Get selected origin
func get_wizard_origin() -> Dictionary:
return _wizard_state.get("selected_origin", {})
## Set selected class
func set_wizard_class(class_data: Dictionary) -> void:
_wizard_state["selected_class"] = class_data
## Get selected class
func get_wizard_class() -> Dictionary:
return _wizard_state.get("selected_class", {})
## Set character name
func set_wizard_name(char_name: String) -> void:
_wizard_state["character_name"] = char_name
## Get character name
func get_wizard_name() -> String:
return _wizard_state.get("character_name", "")
## Set character customization
func set_wizard_customization(customization: Dictionary) -> void:
_wizard_state["character_customization"] = customization
## Get character customization
func get_wizard_customization() -> Dictionary:
return _wizard_state.get("character_customization", {})
## Get complete wizard state
func get_wizard_state() -> Dictionary:
return _wizard_state
## Navigation State Management
## Set current scene
func set_current_scene(scene_path: String) -> void:
if not _current_scene.is_empty():
_scene_history.append(_current_scene)
_current_scene = scene_path
print("[StateManager] Scene changed: %s" % scene_path)
## Get current scene
func get_current_scene() -> String:
return _current_scene
## Navigate back
func navigate_back() -> String:
if _scene_history.is_empty():
return ""
var previous_scene: String = _scene_history.pop_back()
_current_scene = previous_scene
return previous_scene
## Can navigate back
func can_navigate_back() -> bool:
return not _scene_history.is_empty()
## Settings Management
## Get setting value
func get_setting(key: String, default: Variant = null) -> Variant:
return _settings.get(key, default)
## Set setting value
func set_setting(key: String, value: Variant) -> void:
_settings[key] = value
print("[StateManager] Setting updated: %s = %s" % [key, value])
# Auto-save if enabled
if _settings.get("auto_save", true):
save_state()
## Get all settings
func get_settings() -> Dictionary:
return _settings
## Persistence (Save/Load)
## Save state to disk
func save_state() -> void:
var save_data := {
"version": SAVE_VERSION,
"user": _current_user,
"auth_token": _auth_token if _settings.get("remember_login", true) else "",
"session_cookie": http_client.get_session_cookie() if _settings.get("remember_login", true) else "",
"is_authenticated": _is_authenticated if _settings.get("remember_login", true) else false,
"characters": _characters,
"selected_character_id": _selected_character_id,
"character_limits": _character_limits,
"settings": _settings,
"timestamp": Time.get_unix_time_from_system()
}
var file := FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
if file == null:
push_error("[StateManager] Failed to open save file for writing: %s" % FileAccess.get_open_error())
return
file.store_string(JSON.stringify(save_data))
file.close()
print("[StateManager] State saved to %s" % SAVE_FILE_PATH)
## Load state from disk
func _load_state() -> void:
if not FileAccess.file_exists(SAVE_FILE_PATH):
print("[StateManager] No save file found, starting fresh")
return
var file := FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
if file == null:
push_error("[StateManager] Failed to open save file for reading: %s" % FileAccess.get_open_error())
return
var json_string := file.get_as_text()
file.close()
var json := JSON.new()
var parse_error := json.parse(json_string)
if parse_error != OK:
push_error("[StateManager] Failed to parse save file")
return
var save_data: Dictionary = json.data
# Check version
if save_data.get("version", 0) != SAVE_VERSION:
print("[StateManager] Save file version mismatch, ignoring")
return
# Restore state
_current_user = save_data.get("user", {})
_auth_token = save_data.get("auth_token", "")
_is_authenticated = save_data.get("is_authenticated", false)
_characters = []
# Convert characters array
var chars_data = save_data.get("characters", [])
for character in chars_data:
_characters.append(character)
_selected_character_id = save_data.get("selected_character_id", "")
_character_limits = save_data.get("character_limits", {})
_settings = save_data.get("settings", _settings)
# Update HTTPClient with token if authenticated
if _is_authenticated and not _auth_token.is_empty():
http_client.set_auth_token(_auth_token)
# Restore session cookie if present
var session_cookie = save_data.get("session_cookie", "")
if _is_authenticated and not session_cookie.is_empty():
http_client.set_session_cookie(session_cookie)
print("[StateManager] State loaded from %s" % SAVE_FILE_PATH)
print("[StateManager] Authenticated: %s, Characters: %d" % [_is_authenticated, _characters.size()])
## Clear save file
func _clear_save_file() -> void:
if FileAccess.file_exists(SAVE_FILE_PATH):
DirAccess.remove_absolute(SAVE_FILE_PATH)
print("[StateManager] Save file cleared")

View File

@@ -0,0 +1 @@
uid://b28q3ngeah5sf