367 lines
12 KiB
C#
367 lines
12 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description:
|
||
* @Date: 2025年07月24日 星期四 11:07:37
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using ExcelConfig;
|
||
using Logic.Config;
|
||
using Logic.CrashSight;
|
||
using Logic.Multilingual;
|
||
using TH1_Logic.Tools;
|
||
using UnityEngine;
|
||
|
||
|
||
namespace TH1_Logic.Config
|
||
{
|
||
public class ConfigManager
|
||
{
|
||
public static ConfigManager Instance = new ConfigManager();
|
||
public GameConfig Config;
|
||
public VersionConfig VersionCfg;
|
||
private static ColorConfig _colorConfig;
|
||
public static ColorConfig ColorConfig
|
||
{
|
||
get
|
||
{
|
||
if (_colorConfig == null)
|
||
{
|
||
_colorConfig = Resources.Load<ColorConfig>("TH1Config/ColorConfig");
|
||
|
||
}
|
||
return _colorConfig;
|
||
}
|
||
}
|
||
|
||
|
||
public void Init()
|
||
{
|
||
if (Config == null)
|
||
{
|
||
string path = Application.persistentDataPath + "/../Config/game_cfg.json";
|
||
string backupPath = path + ".bak";
|
||
|
||
// 尝试读取主文件
|
||
Config = TryReadGameConfig(path);
|
||
|
||
// 主文件损坏,尝试从备份恢复
|
||
if (Config == null && File.Exists(backupPath))
|
||
{
|
||
LogSystem.LogInfo("[ConfigManager] 主文件损坏,尝试从备份恢复...");
|
||
Config = TryReadGameConfig(backupPath);
|
||
|
||
if (Config != null)
|
||
{
|
||
LogSystem.LogInfo("[ConfigManager] 从备份恢复成功");
|
||
try
|
||
{
|
||
string json = JsonUtility.ToJson(Config);
|
||
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(json);
|
||
FileTools.SafeWriteFile(path, bytes);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogSystem.LogError($"[ConfigManager] 恢复写入主文件失败: {e.Message}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogSystem.LogError("[ConfigManager] 备份文件也已损坏,将重置数据");
|
||
}
|
||
}
|
||
|
||
Config ??= new GameConfig();
|
||
}
|
||
|
||
if (!VersionCfg)
|
||
{
|
||
VersionCfg = Resources.Load<VersionConfig>("Export/VersionConfig");
|
||
}
|
||
CrashSightAgent.SetAppVersion(VersionCfg.CurVersionInfo.FullVersion);
|
||
}
|
||
|
||
public void InitExcelConfig()
|
||
{
|
||
// 获取当前程序集中所有实现 IExcelConfig 接口的类
|
||
var excelConfigTypes = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
|
||
.Where(t => typeof(ExcelConfigBase).IsAssignableFrom(t)
|
||
&& t.IsClass
|
||
&& !t.IsAbstract);
|
||
|
||
foreach (var type in excelConfigTypes)
|
||
{
|
||
try
|
||
{
|
||
// 创建实例
|
||
var instance = (ExcelConfigBase)Activator.CreateInstance(type);
|
||
|
||
// 这里可以根据需要加载对应的配置数据
|
||
// 例如:从 Resources 或其他路径加载 byte[] 数据
|
||
byte[] configData = LoadConfigData(type.Name);
|
||
|
||
// 调用 Init 方法
|
||
instance.Init(configData);
|
||
instance.AfterInit();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogSystem.LogError($"初始化配置失败: {type.Name}, 错误: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
private byte[] LoadConfigData(string typeName)
|
||
{
|
||
string configName = typeName.Replace("Category", "");
|
||
TextAsset asset = Resources.Load<TextAsset>($"ExcelConfig/GenerateBytes/{configName}");
|
||
if (asset == null || asset.bytes == null || asset.bytes.Length == 0)
|
||
{
|
||
LogSystem.LogError($"[ConfigManager] 加载配置数据失败: {configName}");
|
||
return Array.Empty<byte>();
|
||
}
|
||
return asset.bytes;
|
||
}
|
||
|
||
private GameConfig TryReadGameConfig(string path)
|
||
{
|
||
if (!File.Exists(path)) return null;
|
||
|
||
try
|
||
{
|
||
string json = File.ReadAllText(path);
|
||
if (string.IsNullOrEmpty(json))
|
||
{
|
||
LogSystem.LogError($"[ConfigManager] 文件为空: {path}");
|
||
return null;
|
||
}
|
||
|
||
var config = JsonUtility.FromJson<GameConfig>(json);
|
||
if (config == null)
|
||
{
|
||
LogSystem.LogError($"[ConfigManager] 反序列化结果无效: {path}");
|
||
return null;
|
||
}
|
||
|
||
return config;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogSystem.LogError($"[ConfigManager] 读取文件失败: {path} | 错误: {e.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private float _lastSaveTime;
|
||
private const float SaveInterval = 1f; // 最少间隔1秒写一次
|
||
|
||
public void Update()
|
||
{
|
||
if (Config == null || !Config.IsChanged) return;
|
||
if (Time.time - _lastSaveTime < SaveInterval) return;
|
||
|
||
try
|
||
{
|
||
string json = JsonUtility.ToJson(Config);
|
||
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(json);
|
||
string path = Application.persistentDataPath + "/../Config/game_cfg.json";
|
||
FileTools.SafeWriteFile(path, bytes);
|
||
|
||
Config.MarkSaved();
|
||
_lastSaveTime = Time.time;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogSystem.LogError($"[ConfigManager] 保存配置失败: {e.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
[Serializable]
|
||
public class ModLanguageConfig
|
||
{
|
||
public MultilingualType Language;
|
||
/// <summary>
|
||
/// 按优先级排序的 Mod 文件夹路径列表
|
||
/// 索引 0 = 最低优先级(最先应用),最后一项 = 最高优先级(最后应用,可覆盖前面的)
|
||
/// </summary>
|
||
public List<string> ModPaths = new List<string>();
|
||
|
||
public ModLanguageConfig() { }
|
||
public ModLanguageConfig(MultilingualType language) { Language = language; }
|
||
}
|
||
|
||
[Serializable]
|
||
public class GameConfig
|
||
{
|
||
[SerializeField]
|
||
private MultilingualType _multilingualType;
|
||
[SerializeField]
|
||
private float _musicVolume;
|
||
[SerializeField]
|
||
private float _audioVolume;
|
||
[SerializeField]
|
||
private bool _showReminder;
|
||
[SerializeField]
|
||
private bool _keyMomentEnabled;
|
||
[SerializeField]
|
||
private bool _bgmContinuousPlay;
|
||
[SerializeField]
|
||
private List<ModLanguageConfig> _modLanguageConfigs = new List<ModLanguageConfig>();
|
||
private bool _isChanged;
|
||
public bool IsChanged => _isChanged;
|
||
|
||
public void MarkSaved()
|
||
{
|
||
_isChanged = false;
|
||
}
|
||
|
||
|
||
public MultilingualType MultilingualType
|
||
{
|
||
get => _multilingualType;
|
||
set
|
||
{
|
||
if (_multilingualType == value) return;
|
||
_isChanged = true;
|
||
_multilingualType = value;
|
||
}
|
||
}
|
||
|
||
public float MusicVolume
|
||
{
|
||
get => _musicVolume;
|
||
set
|
||
{
|
||
_isChanged = true;
|
||
_musicVolume = value;
|
||
}
|
||
}
|
||
public float AudioVolume
|
||
{
|
||
get => _audioVolume;
|
||
set
|
||
{
|
||
_isChanged = true;
|
||
_audioVolume = value;
|
||
}
|
||
}
|
||
public bool BgmContinuousPlay
|
||
{
|
||
get => _bgmContinuousPlay;
|
||
set
|
||
{
|
||
if (_bgmContinuousPlay == value) return;
|
||
_isChanged = true;
|
||
_bgmContinuousPlay = value;
|
||
}
|
||
}
|
||
public bool ShowReminder
|
||
{
|
||
get => _showReminder;
|
||
set
|
||
{
|
||
if (_showReminder == value) return;
|
||
_isChanged = true;
|
||
_showReminder = value;
|
||
}
|
||
}
|
||
public bool KeyMomentEnabled
|
||
{
|
||
get => _keyMomentEnabled;
|
||
set
|
||
{
|
||
if (_keyMomentEnabled == value) return;
|
||
_isChanged = true;
|
||
_keyMomentEnabled = value;
|
||
}
|
||
}
|
||
|
||
|
||
public GameConfig()
|
||
{
|
||
_multilingualType = MultilingualType.None;
|
||
_musicVolume = 0.5f;
|
||
_audioVolume = 0.5f;
|
||
_showReminder = true;
|
||
_keyMomentEnabled = true;
|
||
_bgmContinuousPlay = false;
|
||
_modLanguageConfigs = new List<ModLanguageConfig>();
|
||
}
|
||
|
||
// ── Mod 优先级配置接口 ──
|
||
|
||
/// <summary>获取所有语种的 Mod 配置列表(只读)</summary>
|
||
public List<ModLanguageConfig> ModLanguageConfigs => _modLanguageConfigs;
|
||
|
||
/// <summary>获取指定语种的 Mod 配置,不存在则创建</summary>
|
||
public ModLanguageConfig GetOrCreateModConfig(MultilingualType language)
|
||
{
|
||
var config = _modLanguageConfigs.Find(c => c.Language == language);
|
||
if (config != null) return config;
|
||
config = new ModLanguageConfig(language);
|
||
_modLanguageConfigs.Add(config);
|
||
_isChanged = true;
|
||
return config;
|
||
}
|
||
|
||
/// <summary>设置指定语种的 Mod 优先级列表(覆盖现有)</summary>
|
||
public void SetModsForLanguage(MultilingualType language, List<string> orderedPaths)
|
||
{
|
||
var config = GetOrCreateModConfig(language);
|
||
config.ModPaths = new List<string>(orderedPaths);
|
||
_isChanged = true;
|
||
}
|
||
|
||
/// <summary>向指定语种添加一个 Mod(加到列表末尾,最高优先级)</summary>
|
||
public void AddModToLanguage(MultilingualType language, string modPath)
|
||
{
|
||
var config = GetOrCreateModConfig(language);
|
||
if (!config.ModPaths.Contains(modPath))
|
||
{
|
||
config.ModPaths.Add(modPath);
|
||
_isChanged = true;
|
||
}
|
||
}
|
||
|
||
/// <summary>从指定语种移除一个 Mod</summary>
|
||
public void RemoveModFromLanguage(MultilingualType language, string modPath)
|
||
{
|
||
var config = _modLanguageConfigs.Find(c => c.Language == language);
|
||
if (config == null) return;
|
||
if (config.ModPaths.Remove(modPath)) _isChanged = true;
|
||
}
|
||
|
||
/// <summary>移动指定语种 Mod 的优先级(向上提升=降低索引,向下降低=增加索引)</summary>
|
||
public void MoveModPriority(MultilingualType language, int fromIndex, int toIndex)
|
||
{
|
||
var config = _modLanguageConfigs.Find(c => c.Language == language);
|
||
if (config == null) return;
|
||
var paths = config.ModPaths;
|
||
if (fromIndex < 0 || fromIndex >= paths.Count) return;
|
||
if (toIndex < 0 || toIndex >= paths.Count) return;
|
||
var item = paths[fromIndex];
|
||
paths.RemoveAt(fromIndex);
|
||
paths.Insert(toIndex, item);
|
||
_isChanged = true;
|
||
}
|
||
|
||
/// <summary>清除指定语种的所有 Mod 配置</summary>
|
||
public void ClearModsForLanguage(MultilingualType language)
|
||
{
|
||
var config = _modLanguageConfigs.Find(c => c.Language == language);
|
||
if (config == null) return;
|
||
if (config.ModPaths.Count > 0)
|
||
{
|
||
config.ModPaths.Clear();
|
||
_isChanged = true;
|
||
}
|
||
}
|
||
}
|
||
} |