using System; using System.Collections.Generic; using UnityEngine; public static class SettingsManager { public static event Action SettingsSaved; public static event Action OnSettingsChanged; private static string SettingsFileName = "GameSettings.json"; private static string SettingsPath => Application.persistentDataPath; public static SettingsSchemeDTO currentSchemeDTO { get; private set; } public static bool Initialized => currentSchemeDTO != null; // Обновленный метод Initialize с обработкой ошибок загрузки public static void Initialize(SettingsScheme defaultSchemeAsset) { LoadSettings(defaultSchemeAsset); } public static void TriggerSettingsChanged() { OnSettingsChanged?.Invoke(); } #region Load/Save/Restore private static void LoadSettings(SettingsScheme defaultSchemeAsset) { try { if (defaultSchemeAsset == null) { Debug.LogError("Default scheme asset is null!"); return; } // Конвертируем дефолтную схему в DTO var defaultDTO = defaultSchemeAsset.GetDTO(); if (defaultDTO == null) { Debug.LogError("Failed to convert default scheme to DTO!"); return; } SettingsSchemeDTO loadedDTO = null; bool loadSuccess = false; try { var loadResult = FileUtil.LoadJson(SettingsFileName, SettingsPath); Debug.Log(loadResult.Message); if (loadResult.OK && loadResult.Data != null) { loadedDTO = loadResult.Data; loadSuccess = true; } } catch (Exception ex) { Debug.LogError($"Error loading settings file: {ex.Message}"); } if (!loadSuccess || loadedDTO == null) { currentSchemeDTO = CloneSettingsScheme(defaultDTO); Debug.Log("Загружена схема настроек по умолчанию (файл отсутствует или поврежден)"); SaveSettings(); return; } string defaultHash = defaultDTO.CalculateHash(); string loadedHash = loadedDTO.CalculateHash(); if (string.Compare(defaultHash, loadedHash, StringComparison.Ordinal) == 0) { currentSchemeDTO = loadedDTO; Debug.Log("Загружена схема настроек из файла"); return; } // Хэши не совпадают - восстанавливаем совместимые настройки Debug.Log("Обнаружено несоответствие схем. Выполняется восстановление настроек..."); currentSchemeDTO = RestoreSettings(defaultDTO, loadedDTO); // Проверяем, что восстановление прошло успешно if (currentSchemeDTO == null) { Debug.LogError("Restore settings failed! Using default settings."); currentSchemeDTO = CloneSettingsScheme(defaultDTO); } SaveSettings(); } catch (Exception ex) { Debug.LogError($"Critical error in Initialize: {ex.Message}\n{ex.StackTrace}"); // Последняя попытка - создать дефолтную схему if (defaultSchemeAsset != null) { currentSchemeDTO = defaultSchemeAsset.GetDTO(); SaveSettings(); } } } private static SettingsSchemeDTO RestoreSettings(SettingsSchemeDTO defaultDTO, SettingsSchemeDTO loadedDTO) { try { // Проверяем входные параметры if (defaultDTO == null) { Debug.LogError("defaultDTO is null! Cannot restore settings."); return null; } if (loadedDTO == null) { Debug.LogWarning("loadedDTO is null, using default settings"); return CloneSettingsScheme(defaultDTO); } // Проверяем, что у defaultDTO есть группы if (defaultDTO.groups == null || defaultDTO.groups.Count == 0) { Debug.LogError("defaultDTO has no groups! Cannot restore settings."); return CloneSettingsScheme(defaultDTO); } var restoredDTO = new SettingsSchemeDTO(); restoredDTO.groups = new List(); // Создаем словарь для быстрого поиска групп и настроек из загруженной схемы var loadedGroupsMap = new Dictionary(); if (loadedDTO.groups != null) { foreach (var group in loadedDTO.groups) { if (group != null && !string.IsNullOrEmpty(group.Name)) { // Если группа с таким именем уже есть, пропускаем дубликат if (!loadedGroupsMap.ContainsKey(group.Name)) { loadedGroupsMap[group.Name] = group; } else { Debug.LogWarning($"Duplicate group name found: {group.Name}, skipping duplicate"); } } } } // Проходим по каждой группе в дефолтной схеме foreach (var defaultGroup in defaultDTO.groups) { if (defaultGroup == null) { Debug.LogWarning("Null group found in defaultDTO, skipping"); continue; } try { var restoredGroup = new SettingsGroupDTO(); restoredGroup.Name = defaultGroup.Name; restoredGroup.IconPath = defaultGroup.IconPath; restoredGroup.Settings = new List(); // Создаем словарь настроек загруженной группы (если она существует) var loadedSettingsMap = new Dictionary(); if (loadedGroupsMap.TryGetValue(defaultGroup.Name, out var loadedGroup) && loadedGroup != null) { if (loadedGroup.Settings != null) { foreach (var setting in loadedGroup.Settings) { if (setting != null && !string.IsNullOrEmpty(setting.Id)) { try { string settingHash = setting.GetStringForHash(); if (!loadedSettingsMap.ContainsKey(settingHash)) { loadedSettingsMap[settingHash] = setting; } } catch (Exception ex) { Debug.LogError($"Error calculating hash for setting {setting.Id}: {ex.Message}"); } } } } } // Проходим по каждой настройке в дефолтной группе if (defaultGroup.Settings != null) { foreach (var defaultSetting in defaultGroup.Settings) { if (defaultSetting == null) { Debug.LogWarning("Null setting found in default group, skipping"); continue; } try { string defaultSettingHash = defaultSetting.GetStringForHash(); SettingDTO finalSetting = null; // Пытаемся найти совместимую настройку if (loadedSettingsMap.TryGetValue(defaultSettingHash, out var compatibleSetting)) { // Нашли - берем значение из загруженной настройки finalSetting = CopySettingValue(compatibleSetting, defaultSetting); Debug.Log($"Восстановлена настройка: {defaultSetting.Id} из группы {defaultGroup.Name}"); } else { // Не нашли - берем значение по умолчанию finalSetting = CopySettingStructure(defaultSetting); Debug.Log($"Использовано значение по умолчанию для настройки: {defaultSetting.Id} из группы {defaultGroup.Name}"); } if (finalSetting != null) { restoredGroup.Settings.Add(finalSetting); } } catch (Exception ex) { Debug.LogError($"Error restoring setting {defaultSetting.Id} from group {defaultGroup.Name}: {ex.Message}"); // Добавляем настройку по умолчанию в случае ошибки var fallbackSetting = CopySettingStructure(defaultSetting); if (fallbackSetting != null) { restoredGroup.Settings.Add(fallbackSetting); } } } } restoredDTO.groups.Add(restoredGroup); } catch (Exception ex) { Debug.LogError($"Error restoring group {defaultGroup.Name}: {ex.Message}"); // Пытаемся добавить группу с настройками по умолчанию try { var fallbackGroup = CopyGroupStructure(defaultGroup); if (fallbackGroup != null) { restoredDTO.groups.Add(fallbackGroup); } } catch (Exception fallbackEx) { Debug.LogError($"Failed to create fallback group {defaultGroup.Name}: {fallbackEx.Message}"); } } } // Проверяем, что у нас есть хотя бы одна группа if (restoredDTO.groups == null || restoredDTO.groups.Count == 0) { Debug.LogError("Failed to restore any groups! Using default settings."); return CloneSettingsScheme(defaultDTO); } Debug.Log($"Настройки восстановлены успешно. Групп: {restoredDTO.groups.Count}"); return restoredDTO; } catch (Exception ex) { Debug.LogError($"Critical error in RestoreSettings: {ex.Message}\n{ex.StackTrace}"); // В случае критической ошибки возвращаем копию дефолтной схемы return CloneSettingsScheme(defaultDTO); } } // Вспомогательный метод для создания глубокой копии схемы private static SettingsSchemeDTO CloneSettingsScheme(SettingsSchemeDTO source) { if (source == null) return null; try { var clone = new SettingsSchemeDTO(); clone.groups = new List(); if (source.groups != null) { foreach (var group in source.groups) { if (group != null) { var groupClone = CopyGroupStructure(group); if (groupClone != null) { clone.groups.Add(groupClone); } } } } return clone; } catch (Exception ex) { Debug.LogError($"Error cloning settings scheme: {ex.Message}"); return null; } } // Вспомогательный метод для копирования группы private static SettingsGroupDTO CopyGroupStructure(SettingsGroupDTO source) { if (source == null) return null; try { var group = new SettingsGroupDTO(); group.Name = source.Name; group.IconPath = source.IconPath; group.Settings = new List(); if (source.Settings != null) { foreach (var setting in source.Settings) { if (setting != null) { var settingClone = CopySettingStructure(setting); if (settingClone != null) { group.Settings.Add(settingClone); } } } } return group; } catch (Exception ex) { Debug.LogError($"Error copying group {source?.Name}: {ex.Message}"); return null; } } private static SettingDTO CopySettingValue(SettingDTO source, SettingDTO targetTemplate) { var newSetting = new SettingDTO(); // Копируем структуру из шаблона newSetting.Type = targetTemplate.Type; newSetting.Id = targetTemplate.Id; newSetting.Alias = targetTemplate.Alias; newSetting.Description = targetTemplate.Description; newSetting.IntMin = targetTemplate.IntMin; newSetting.IntMax = targetTemplate.IntMax; newSetting.IntStep = targetTemplate.IntStep; newSetting.FloatMin = targetTemplate.FloatMin; newSetting.FloatMax = targetTemplate.FloatMax; newSetting.FloatStep = targetTemplate.FloatStep; if (targetTemplate.AllowedValues != null) { newSetting.AllowedValues = new List(targetTemplate.AllowedValues); } // Копируем значение из source switch (targetTemplate.Type) { case Setting.SettingType.Bool: newSetting.BoolValue = source.BoolValue; break; case Setting.SettingType.Int: newSetting.IntValue = Mathf.Clamp(source.IntValue, targetTemplate.IntMin, targetTemplate.IntMax); break; case Setting.SettingType.Float: newSetting.FloatValue = Mathf.Clamp(source.FloatValue, targetTemplate.FloatMin, targetTemplate.FloatMax); break; case Setting.SettingType.String: if (targetTemplate.AllowedValues != null && targetTemplate.AllowedValues.Contains(source.StringValue)) { newSetting.StringValue = source.StringValue; } else { newSetting.StringValue = targetTemplate.StringValue; } break; case Setting.SettingType.Signal: newSetting.SignalValue = targetTemplate.SignalValue; break; } return newSetting; } private static SettingDTO CopySettingStructure(SettingDTO template) { var newSetting = new SettingDTO(); newSetting.Type = template.Type; newSetting.Id = template.Id; newSetting.Alias = template.Alias; newSetting.Description = template.Description; newSetting.IntMin = template.IntMin; newSetting.IntMax = template.IntMax; newSetting.IntStep = template.IntStep; newSetting.FloatMin = template.FloatMin; newSetting.FloatMax = template.FloatMax; newSetting.FloatStep = template.FloatStep; newSetting.BoolValue = template.BoolValue; newSetting.IntValue = template.IntValue; newSetting.FloatValue = template.FloatValue; newSetting.StringValue = template.StringValue; newSetting.SignalValue = template.SignalValue; if (template.AllowedValues != null) { newSetting.AllowedValues = new List(template.AllowedValues); } return newSetting; } public static void SaveSettings() { if (currentSchemeDTO != null) { FileUtil.SaveJson(SettingsFileName, SettingsPath, currentSchemeDTO); SettingsSaved?.Invoke(); Debug.Log("Настройки сохранены"); } } #endregion #region Get Values private static T GetSetting(string id, Setting.SettingType expectedType, Func getValue) { if (!IsInitialized()) throw new InvalidOperationException("Менеджер настроек не инициализирован"); var setting = FindSetting(id); if (setting == null) throw new InvalidOperationException($"Настройка {id} не найдена"); if (setting.Type != expectedType) throw new InvalidOperationException($"Настройка {id} не соответствует ожидаемому типу {expectedType}"); return getValue(setting); } public static string GetString(string id) => GetSetting(id, Setting.SettingType.String, s => s.StringValue); public static int GetInt(string id) => GetSetting(id, Setting.SettingType.Int, s => s.IntValue); public static float GetFloat(string id) => GetSetting(id, Setting.SettingType.Float, s => s.FloatValue); public static bool GetBool(string id) => GetSetting(id, Setting.SettingType.Bool, s => s.BoolValue); public static void SetBool(string id, bool value) { SettingDTO setting = FindSetting(id); if (setting == null) throw new InvalidOperationException($"Настройка {id} не найдена"); if (setting.Type != Setting.SettingType.Bool) throw new InvalidOperationException($"Настройка {id} не является логической"); if (setting.BoolValue == value) return; setting.BoolValue = value; TriggerSettingsChanged(); SaveSettings(); } #endregion #region Private Helpers private static bool IsInitialized() { if (currentSchemeDTO == null) { Debug.LogError("SettingsManager not initialized! Call Initialize() first."); return false; } return true; } private static SettingDTO FindSetting(string id) { foreach (var group in currentSchemeDTO.groups) { if (group?.Settings == null) continue; foreach (var setting in group.Settings) { if (setting != null && setting.Id == id) { return setting; } } } Debug.LogWarning($"Setting not found: {id}"); return null; } #endregion }