423 lines
11 KiB
GDScript
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")
|