83 lines
3.5 KiB
C#
83 lines
3.5 KiB
C#
using System.Text.Json;
|
||||
|
|
using Ananta.Server.Configuration;
|
|||
|
|
|
|||
|
|
namespace Ananta.Server.Handlers.Game;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 普通自动驾驶巡航速度的<b>独立</b>设置(2026-09-30)。
|
|||
|
|
///
|
|||
|
|
/// ★★ 为什么单独一个 JSON 而不写进 <c>private-server.json</c>:
|
|||
|
|
/// ① GM 面板的 <c>/api/settings</c> 是<b>整份回写</b>,两个写者容易互相覆盖;
|
|||
|
|
/// ② 主配置改完要走 <c>/api/reload-config</c>,而调速希望「保存即可用」;
|
|||
|
|
/// ③ 回滚只要删这一个文件,不动任何别的开关。
|
|||
|
|
/// ⇒ 本类<b>绝不</b>改写 <c>private-server.json</c>。
|
|||
|
|
///
|
|||
|
|
/// 单位:对外(GM / JSON)统一 <b>km/h</b>;服务端下发的 Cruise 参数是 <b>m/s</b>,
|
|||
|
|
/// 换算在 <c>GameRouter.VehicleNav.cs</c> 的 <c>EffectiveCruiseSpeedMs</c> 里做,别在这里除。
|
|||
|
|
/// 默认 43.2 km/h = 12 m/s,与 <c>gameplay.vehicleNavigation.cruiseSpeed</c> 的默认值一致。
|
|||
|
|
/// </summary>
|
|||
|
|
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;
|
|||
|
|
|
|||
|
|
/// <summary>读取当前速度(km/h)。文件不存在 = 用默认值。</summary>
|
|||
|
|
internal static float Read()
|
|||
|
|
{
|
|||
|
|
lock (Gate)
|
|||
|
|
{
|
|||
|
|
if (!File.Exists(FilePath)) return DefaultKmh;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var value = JsonSerializer.Deserialize<Settings>(
|
|||
|
|
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 的速度。");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>保存速度(km/h),原子写(临时文件 + move),返回实际落盘值。</summary>
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|