Files
2025-11-24 23:10:55 -06:00

423 lines
11 KiB
GDScript

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