using System.Collections.Generic; using UnityEngine; using System.IO; [CreateAssetMenu(fileName = "SettingsData", menuName = "Settings/SettingsData", order = 0)] public class SettingsData : ScriptableObject { [Tooltip("Список дефолтных настроек, будет использован при первом запуске или при сбросе")] public List defaultSettings = new List(); public List GetDefaults() { if (defaultSettings != null && defaultSettings.Count > 0) return defaultSettings; // fallback: возвращаем встроенные дефолты return CreateBuiltinDefaults(); } // Создаёт новый GameSettings (клонированный) на основе defaultSettings или встраиваемых дефолтов public GameSettings CreateGameSettings() { var defaults = GetDefaults(); var gs = new GameSettings(); gs.settings = new List(); foreach (var s in defaults) { gs.settings.Add(new SettingOption(s.settingName, new List(s.options), s.defaultIndex, s.type, s.panelId)); } gs.hash = ComputeHash(gs.settings); return gs; } // Создаёт GameSettings на основе встроенных дефолтов (статический helper) public static GameSettings CreateGameSettingsFromDefaults() { var defaults = CreateBuiltinDefaults(); var gs = new GameSettings(); gs.settings = new List(); foreach (var s in defaults) { gs.settings.Add(new SettingOption(s.settingName, new List(s.options), s.defaultIndex, s.type, s.panelId)); } gs.hash = ComputeHash(gs.settings); return gs; } private static int ComputeHash(List settings) { var sb = new System.Text.StringBuilder(); foreach (var option in settings) { sb.Append(option.settingName); foreach (var o in option.options) { sb.Append(o); } } return sb.ToString().GetHashCode(); } public static List CreateBuiltinDefaults() { List settings = new List(); settings.Add(new SettingOption(settingName: "Громкость музыки", options: new List() { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }, defaultIndex: 1)); settings.Add(new SettingOption(settingName: "Громкость эффектов", options: new List() { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }, defaultIndex: 8)); settings.Add(new SettingOption(settingName: "Громкость голоса", options: new List() { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }, defaultIndex: 8)); settings.Add(new SettingOption(settingName: "QR-код для связи", options: new List() { "Telegram", "WhatsApp" ,"Всё"}, defaultIndex: 0)); settings.Add(new SettingOption(settingName: "Игровая тема", options: new List() { "День","День_2" ,"Ночь" }, defaultIndex: 0)); settings.Add(new SettingOption(settingName: "Режим игры", options: new List() { "Классика", "Растущие поля", "Рифы" }, defaultIndex: 0)); settings.Add(new SettingOption(settingName: "Выбор режима игроком", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Выдача призов при игре с ботом", options: new List() { "Выдавать", "Не выдавать" }, defaultIndex: 0, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Бесплатная игра", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Общий баланс", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Скидка за реванш при игре с ботом", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Размер скидки за реванш", options: new List() { "10", "20", "30", "40", "50" }, defaultIndex: 1)); // по умолчанию 20% settings.Add(new SettingOption(settingName: "Показывать контакты администрации", options: new List() { "Вкл", "Выкл" }, defaultIndex: 0, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Быстрая проверка", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Быстрая проверка наличия приза", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Выдавать приз при ошибке", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Вывод ошибки выдачи в меню", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Вывод информации о суперпризе в меню", options: new List() { "Вкл", "Выкл" }, defaultIndex: 1, type: SettingType.Svitcher)); settings.Add(new SettingOption(settingName: "Показывать цену игры", options: new List() { "Вкл", "Выкл" }, defaultIndex: 0, type: SettingType.Svitcher)); // Настройки-панели (открываются удержанием) settings.Add(new SettingOption( settingName: "Ввести номер телефона администрации", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 0)); settings.Add(new SettingOption( settingName: "Ввести номер для связи в WhatsApp", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 1)); settings.Add(new SettingOption( settingName: "Ввести номер для связи в Telegram", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 2)); settings.Add(new SettingOption( settingName: "Задать часоту выдачи специального приза", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 3)); settings.Add(new SettingOption( settingName: "Задать суперприз", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 4)); settings.Add(new SettingOption( settingName: "Настройка персонажа", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 5)); settings.Add(new SettingOption( settingName: "Задать цену игры с ботом", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 6)); settings.Add(new SettingOption( settingName: "Задать цену игры с другом", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 7)); settings.Add(new SettingOption( settingName: "Задать цену импульса", options: new List() { "" }, defaultIndex: 0, type: SettingType.Panel, panelId: 8)); // Настройки-экшены (выполняются удержанием) settings.Add(new SettingOption( settingName: "Сбросить счетчик монет", options: new List() { "" }, defaultIndex: 0, type: SettingType.Action, panelId: 100)); settings.Add(new SettingOption( settingName: "Сброс статистики призов", options: new List() { "" }, defaultIndex: 0, type: SettingType.Action, panelId: 101)); settings.Add(new SettingOption( settingName: "Сброс прогресса до суперприза", options: new List() { "" }, defaultIndex: 0, type: SettingType.Action, panelId: 102)); return settings; } // Попытка загрузить GameSettings из JSON-файла. Возвращает null при ошибке/отсутствии файла. public GameSettings TryLoadFromFile(string path) { try { if (!File.Exists(path)) return null; string json = File.ReadAllText(path, System.Text.Encoding.UTF8); var gs = JsonUtility.FromJson(json); return gs; } catch (System.Exception e) { Debug.LogWarning($"SettingsData.TryLoadFromFile failed: {e.Message}"); return null; } } // Попытка сохранить GameSettings в файл. Возвращает true при успехе. public bool TrySaveToFile(string path, GameSettings gs) { try { if (gs == null) return false; string json = JsonUtility.ToJson(gs); File.WriteAllText(path, json, System.Text.Encoding.UTF8); return true; } catch (System.Exception e) { Debug.LogError($"SettingsData.TrySaveToFile failed: {e.Message}"); return false; } } // Загрузить из файла или создать дефолтный GameSettings и сохранить его public GameSettings LoadOrCreateFromFile(string path) { var loaded = TryLoadFromFile(path); if (loaded != null) { int currentHash = GetDefaultsHash(); if (loaded.hash != currentHash) { // Хеш изменился — значит добавились/удалились настройки. // Делаем merge: пользовательские значения сохраняются, новые добавляются с дефолтом. Debug.Log($"[SettingsData] Хеш изменился ({loaded.hash} -> {currentHash}). Обновляем настройки..."); loaded = MergeWithDefaults(loaded, GetDefaults()); loaded.hash = currentHash; TrySaveToFile(path, loaded); } return loaded; } var created = CreateGameSettings(); TrySaveToFile(path, created); return created; } // Слияние загруженных настроек с дефолтами. // Пользовательские значения сохраняются, новые настройки добавляются с defaultIndex, удалённые отбрасываются. private GameSettings MergeWithDefaults(GameSettings loaded, List defaults) { var merged = new GameSettings(); merged.settings = new List(); foreach (var def in defaults) { var existing = loaded.settings?.Find(s => s.settingName == def.settingName); if (existing != null) { // Настройка уже была — берём пользовательский chosenIndex, но обновляем всё остальное из дефолта int safeIndex = (existing.chosenIndex >= 0 && existing.chosenIndex < def.options.Count) ? existing.chosenIndex : def.defaultIndex; merged.settings.Add(new SettingOption(def.settingName, new List(def.options), def.defaultIndex, def.type, def.panelId) { chosenIndex = safeIndex }); } else { // Новая настройка — добавляем с дефолтным значением Debug.Log($"[SettingsData] Новая настройка: '{def.settingName}' (defaultIndex={def.defaultIndex})"); merged.settings.Add(new SettingOption(def.settingName, new List(def.options), def.defaultIndex, def.type, def.panelId)); } } return merged; } // Попытка загрузить GameSettings из файла (статическая, не требует экземпляра SettingsData) public static GameSettings LoadFromFile(string path) { try { if (!File.Exists(path)) return null; string json = File.ReadAllText(path, System.Text.Encoding.UTF8); var gs = JsonUtility.FromJson(json); return gs; } catch (System.Exception e) { Debug.LogWarning($"SettingsData.LoadFromFile failed: {e.Message}"); return null; } } // Сохранить GameSettings в файл (статическая версия) public static bool SaveToFile(string path, GameSettings gs) { try { if (gs == null) return false; string json = JsonUtility.ToJson(gs); File.WriteAllText(path, json, System.Text.Encoding.UTF8); return true; } catch (System.Exception e) { Debug.LogError($"SettingsData.SaveToFile failed: {e.Message}"); return false; } } // Статическая реализация LoadOrCreate: пытается загрузить, иначе создаёт из встроенных дефолтов и сохраняет public static GameSettings LoadOrCreateFromFileStatic(string path) { var loaded = LoadFromFile(path); if (loaded != null) { var defaults = CreateBuiltinDefaults(); int currentHash = ComputeHash(defaults); if (loaded.hash != currentHash) { // Хеш изменился — делаем merge, пользовательские значения не трогаем Debug.Log($"[SettingsData] Хеш изменился ({loaded.hash} -> {currentHash}). Обновляем настройки..."); loaded = MergeWithDefaultsStatic(loaded, defaults, currentHash); SaveToFile(path, loaded); } return loaded; } var created = CreateGameSettingsFromDefaults(); SaveToFile(path, created); return created; } // Статическая версия merge (используется когда нет экземпляра SettingsData) private static GameSettings MergeWithDefaultsStatic(GameSettings loaded, List defaults, int newHash) { var merged = new GameSettings(); merged.settings = new List(); merged.hash = newHash; foreach (var def in defaults) { var existing = loaded.settings?.Find(s => s.settingName == def.settingName); if (existing != null) { // Настройка уже была — сохраняем пользовательский chosenIndex int safeIndex = (existing.chosenIndex >= 0 && existing.chosenIndex < def.options.Count) ? existing.chosenIndex : def.defaultIndex; merged.settings.Add(new SettingOption(def.settingName, new List(def.options), def.defaultIndex, def.type, def.panelId) { chosenIndex = safeIndex }); } else { // Новая настройка — добавляем с дефолтным значением Debug.Log($"[SettingsData] Новая настройка: '{def.settingName}' (defaultIndex={def.defaultIndex})"); merged.settings.Add(new SettingOption(def.settingName, new List(def.options), def.defaultIndex, def.type, def.panelId)); } } return merged; } const string DefaultFileName = "settings.json"; public static string GetDefaultFilePath() { return Path.Combine(Application.persistentDataPath, DefaultFileName); } // Обертки: статические версии, использующие путь по умолчанию public static GameSettings LoadFromDefaultPath() { return LoadFromFile(GetDefaultFilePath()); } public static bool SaveToDefaultPath(GameSettings gs) { return SaveToFile(GetDefaultFilePath(), gs); } public static GameSettings LoadOrCreateFromFileDefault() { return LoadOrCreateFromFileStatic(GetDefaultFilePath()); } // Экземплярные обёртки (работают через этот ScriptableObject) public GameSettings TryLoadFromDefaultPath() { return TryLoadFromFile(GetDefaultFilePath()); } public bool TrySaveToDefaultPath(GameSettings gs) { return TrySaveToFile(GetDefaultFilePath(), gs); } public GameSettings LoadOrCreateFromDefaultPath() { return LoadOrCreateFromFile(GetDefaultFilePath()); } // Возвращает хеш дефолтов (чтобы не создавать GameSettings в менеджере) public int GetDefaultsHash() { return ComputeHash(GetDefaults()); } // Instance wrapper для сохранения GameSettings в путь по умолчанию public bool SaveGameSettings(GameSettings gs) { return TrySaveToDefaultPath(gs); } // Instance wrapper для загрузки GameSettings из пути по умолчанию public GameSettings LoadGameSettings() { return TryLoadFromDefaultPath(); } // Instance wrapper LoadOrCreate public GameSettings LoadOrCreateGameSettings() { return LoadOrCreateFromDefaultPath(); } }