extends Node ## Autoload for local persistence (JSON in user://). Wrapped in an autoload so UI ## nodes never touch the filesystem directly. const SHIFT_RESULTS_FILE := "shift_results.json" const SETTINGS_FILE := "settings.json" const CAMPAIGN_FILE := "campaign.json" const SHIFT_STATE_FILE := "shift_state.json" func _load_json(file_name: String) -> Variant: var path := "user://" + file_name if not FileAccess.file_exists(path): return null var text := FileAccess.get_file_as_string(path) if text.is_empty(): return null var parsed = JSON.parse_string(text) return parsed func _save_json(file_name: String, data: Variant) -> Error: var path := "user://" + file_name var temp := path + ".tmp" var text := JSON.stringify(data, "\t") var f := FileAccess.open(temp, FileAccess.WRITE) if f == null: return FileAccess.get_open_error() f.store_string(text) f.close() # Replace the target only after the complete JSON has been written, so a # crash cannot leave a partial save in place. return DirAccess.rename_absolute(ProjectSettings.globalize_path(temp), ProjectSettings.globalize_path(path)) ## Load previous shift results. Returns Array of Dictionaries or []. func load_shift_results() -> Array: var data = _load_json(SHIFT_RESULTS_FILE) if data is Array: return data return [] ## Append one shift result and save. Returns Error. func save_shift_result(result: Dictionary) -> Error: var results := load_shift_results() results.append(result) return _save_json(SHIFT_RESULTS_FILE, results) ## Campaign/session progression (separate from settings and shift history). func load_campaign() -> Dictionary: var data = _load_json(CAMPAIGN_FILE) return data if data is Dictionary else {} func save_campaign(data: Dictionary) -> Error: return _save_json(CAMPAIGN_FILE, data) func reset_campaign() -> Error: return _save_json(CAMPAIGN_FILE, {}) ## Mid-shift checkpoint. Kept separate so a completed shift history is never ## overwritten by a resumable snapshot. func load_shift_state() -> Dictionary: var data = _load_json(SHIFT_STATE_FILE) return data if data is Dictionary else {} func save_shift_state(data: Dictionary) -> Error: return _save_json(SHIFT_STATE_FILE, data) func clear_shift_state() -> Error: return _save_json(SHIFT_STATE_FILE, {}) ## Defaults for player settings. Kept in code so a missing or partial ## settings.json still produces a usable config. const DEFAULT_SETTINGS := { "look_sensitivity": 0.0022, "hud_font_scale": 1.0, "invert_y": false, "head_bob": true, "input_bindings": {}, "ai_difficulty": "normal", "tutorial_completed": false, "tutorial_skipped": false, "master_volume": 1.0, "sfx_volume": 1.0, "ambience_volume": 1.0, "reduce_motion": false, "captions_enabled": true, } ## Load player settings, merged over the defaults. Returns a Dictionary. func load_settings() -> Dictionary: var merged: Dictionary = DEFAULT_SETTINGS.duplicate(true) var data = _load_json(SETTINGS_FILE) if data is Dictionary: for key in data.keys(): if merged.has(key): merged[key] = data[key] return merged ## Persist player settings. Returns Error. func save_settings(settings: Dictionary) -> Error: var merged: Dictionary = DEFAULT_SETTINGS.duplicate(true) for key in settings.keys(): if merged.has(key): merged[key] = settings[key] return _save_json(SETTINGS_FILE, merged)