using System.Text.Json; using Ananta.Server.Configuration; namespace Ananta.Server.Handlers.Game; /// /// 普通自动驾驶巡航速度的独立设置(2026-09-30)。 /// /// ★★ 为什么单独一个 JSON 而不写进 private-server.json: /// ① GM 面板的 /api/settings 是整份回写,两个写者容易互相覆盖; /// ② 主配置改完要走 /api/reload-config,而调速希望「保存即可用」; /// ③ 回滚只要删这一个文件,不动任何别的开关。 /// ⇒ 本类绝不改写 private-server.json。 /// /// 单位:对外(GM / JSON)统一 km/h;服务端下发的 Cruise 参数是 m/s, /// 换算在 GameRouter.VehicleNav.cs 的 EffectiveCruiseSpeedMs 里做,别在这里除。 /// 默认 43.2 km/h = 12 m/s,与 gameplay.vehicleNavigation.cruiseSpeed 的默认值一致。 /// internal static class AutonomousDriveSpeedSettings { internal const float DefaultKmh = 43.2f, MinKmh = 10f, MaxKmh = 120f; private static readonly object Gate = new(); // ⛑ 用最朴素的可序列化形状(internal 类 + public 无参构造 + 可写属性), // 不用私有嵌套 record:后者要靠参数化构造绑定,出了问题是运行时才炸。 internal sealed class Settings { public float SpeedKmh { get; set; } = DefaultKmh; } internal static string FilePath => Path.Combine(PrivateServerConfigStore.ProjectRoot, "config", "autonomous-driving.json"); internal static bool Valid(float kmh) => float.IsFinite(kmh) && kmh >= MinKmh && kmh <= MaxKmh; /// 读取当前速度(km/h)。文件不存在 = 用默认值。 internal static float Read() { lock (Gate) { if (!File.Exists(FilePath)) return DefaultKmh; try { var value = JsonSerializer.Deserialize( File.ReadAllText(FilePath), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (value is not null && Valid(value.SpeedKmh)) return value.SpeedKmh; } catch (JsonException) { // 落到下面统一抛:让 GM 显示「请重新保存」而不是静默用默认值。 } throw new InvalidDataException( $"自动驾驶速度配置无效({FilePath}),请通过 GM 保存 {MinKmh:0.#}–{MaxKmh:0.#} km/h 的速度。"); } } /// 保存速度(km/h),原子写(临时文件 + move),返回实际落盘值。 internal static float Save(float kmh) { if (!Valid(kmh)) throw new ArgumentOutOfRangeException(nameof(kmh), $"速度必须为 {MinKmh:0.#}–{MaxKmh:0.#} km/h。"); kmh = MathF.Round(kmh, 1); lock (Gate) { var path = FilePath; Directory.CreateDirectory(Path.GetDirectoryName(path)!); var temp = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { File.WriteAllText(temp, JsonSerializer.Serialize(new Settings { SpeedKmh = kmh }, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true })); File.Move(temp, path, overwrite: true); } finally { if (File.Exists(temp)) File.Delete(temp); } } return kmh; } }