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