supervisor-simulator/scripts/Core/ContentRegistry.cs
2026-01-18 20:05:23 +08:00

298 lines
9.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using Godot;
using Models;
namespace Core;
/// <summary>
/// 内容加载与合并(基础 + Mod
/// 设计说明:
/// 1) 通过 IContentSource 抽象读取来源,支持 res:// 与 user://mods。
/// 2) ContentRegistry 负责合并,同 Id 以后加载覆盖先加载。
/// 注意事项:
/// - 真实加载逻辑应避免在主线程做大规模 IO。
/// 未来扩展:
/// - 可加入“补丁合并策略”(例如列表合并/字段覆盖)。
/// </summary>
public interface IContentSource {
/// <summary>
/// 优先级
/// </summary>
int Priority { get; }
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
IEnumerable<T> LoadAll<T>() where T : class;
}
/// <summary>
/// 内容合并模式
/// </summary>
public enum ContentMergeMode {
Override, // 覆盖
KeepFirst // 保留首次
}
/// <summary>
/// 内容注册表
/// </summary>
public sealed class ContentRegistry {
private readonly List<IContentSource> _sources = new();
/// <summary>
/// 合并模式
/// </summary>
public ContentMergeMode MergeMode { get; set; } = ContentMergeMode.Override;
/// <summary>
/// 注册内容源
/// </summary>
/// <param name="source">内容源</param>
public void RegisterSource(IContentSource source) {
_sources.Add(source);
_sources.Sort((a, b) => a.Priority.CompareTo(b.Priority));
}
/// <summary>
/// 构建游戏内容数据库
/// </summary>
/// <returns>游戏内容数据库</returns>
public GameContentDatabase BuildDatabase() {
var db = new GameContentDatabase();
Merge(db.Disciplines, LoadAll<DisciplineDefinition>(), d => d.Header.Id);
Merge(db.Archetypes, LoadAll<ArchetypeDefinition>(), d => d.Header.Id);
Merge(db.Roles, LoadAll<RoleDefinition>(), d => d.Header.Id);
Merge(db.Traits, LoadAll<TraitDefinition>(), d => d.Header.Id);
Merge(db.Tasks, LoadAll<TaskDefinition>(), d => d.Header.Id);
Merge(db.Items, LoadAll<ItemDefinition>(), d => d.Header.Id);
Merge(db.Papers, LoadAll<PaperDefinition>(), d => d.Header.Id);
Merge(db.RoguelitePerks, LoadAll<RoguelitePerkDefinition>(), d => d.Header.Id);
return db;
}
/// <summary>
/// 从所有源加载指定类型的所有对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
private IEnumerable<T> LoadAll<T>() where T : class {
foreach (var source in _sources)
foreach (var item in source.LoadAll<T>())
yield return item;
}
/// <summary>
/// 合并对象到目标字典
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="target">目标字典</param>
/// <param name="items">对象集合</param>
/// <param name="idSelector">ID选择器</param>
private void Merge<T>(Dictionary<string, T> target, IEnumerable<T> items, Func<T, string> idSelector)
where T : class {
foreach (var item in items) {
var id = idSelector(item);
if (string.IsNullOrWhiteSpace(id)) continue;
if (target.ContainsKey(id)) {
if (MergeMode == ContentMergeMode.Override) target[id] = item;
}
else {
target[id] = item;
}
}
}
}
/// <summary>
/// 资源读取示例res:// 中的 .tres/.res
/// 这里只给出接口骨架,具体解析留给后续实现。
/// </summary>
public sealed class ResourceContentSource : IContentSource {
/// <summary>
/// 构造函数
/// </summary>
/// <param name="priority">优先级</param>
public ResourceContentSource(int priority) {
Priority = priority;
}
/// <summary>
/// 资源路径列表
/// </summary>
public List<string> ResourcePaths { get; } = new();
/// <summary>
/// 优先级
/// </summary>
public int Priority { get; }
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
public IEnumerable<T> LoadAll<T>() where T : class {
foreach (var path in ResourcePaths) {
if (string.IsNullOrWhiteSpace(path)) continue;
var resource = ResourceLoader.Load<Resource>(path);
if (resource == null) continue;
foreach (var item in ExtractResources<T>(resource)) yield return item;
}
}
/// <summary>
/// 从资源中提取对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="resource">资源</param>
/// <returns>对象集合</returns>
private IEnumerable<T> ExtractResources<T>(Resource resource) where T : class {
if (resource is IContentResource content) {
if (content.GetDefinitionType() == typeof(T))
if (content.ToDefinition() is T typed)
yield return typed;
yield break;
}
if (resource is IContentResourceCollection collection)
foreach (var item in collection.GetItems())
if (item.GetDefinitionType() == typeof(T))
if (item.ToDefinition() is T typed)
yield return typed;
}
}
/// <summary>
/// 资源读取示例json/yaml/自定义格式Mod 友好)
/// </summary>
public sealed class JsonContentSource : IContentSource {
private readonly JsonSerializerOptions _options;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="priority">优先级</param>
public JsonContentSource(int priority) {
Priority = priority;
_options = new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
};
_options.Converters.Add(new JsonStringEnumConverter());
}
/// <summary>
/// 数据路径列表
/// </summary>
public List<string> DataPaths { get; } = new();
/// <summary>
/// 优先级
/// </summary>
public int Priority { get; }
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
public IEnumerable<T> LoadAll<T>() where T : class {
foreach (var path in DataPaths) {
if (string.IsNullOrWhiteSpace(path)) continue;
var resolvedPath = ResolvePath(path);
if (!File.Exists(resolvedPath)) continue;
var json = File.ReadAllText(resolvedPath);
if (string.IsNullOrWhiteSpace(json)) continue;
if (TryDeserializeList(json, out List<T> list)) {
foreach (var item in list) yield return item;
continue;
}
if (TryDeserializeEnvelope(json, out JsonContentEnvelope<T> envelope)) {
if (envelope.Items != null)
foreach (var item in envelope.Items)
yield return item;
else if (envelope.Item != null) yield return envelope.Item;
continue;
}
if (TryDeserializeSingle(json, out T single)) yield return single;
}
}
/// <summary>
/// 解析路径
/// </summary>
/// <param name="path">路径</param>
/// <returns>解析后的绝对路径</returns>
private string ResolvePath(string path) {
if (path.StartsWith("res://") || path.StartsWith("user://")) return ProjectSettings.GlobalizePath(path);
return path;
}
/// <summary>
/// 尝试反序列化列表
/// </summary>
private bool TryDeserializeList<T>(string json, out List<T> list) where T : class {
try {
list = JsonSerializer.Deserialize<List<T>>(json, _options);
return list != null;
}
catch {
list = null;
return false;
}
}
/// <summary>
/// 尝试反序列化单个对象
/// </summary>
private bool TryDeserializeSingle<T>(string json, out T item) where T : class {
try {
item = JsonSerializer.Deserialize<T>(json, _options);
return item != null;
}
catch {
item = null;
return false;
}
}
/// <summary>
/// 尝试反序列化信封结构
/// </summary>
private bool TryDeserializeEnvelope<T>(string json, out JsonContentEnvelope<T> envelope) where T : class {
try {
envelope = JsonSerializer.Deserialize<JsonContentEnvelope<T>>(json, _options);
if (envelope == null) return false;
return envelope.Items != null || envelope.Item != null;
}
catch {
envelope = null;
return false;
}
}
private sealed class JsonContentEnvelope<T> where T : class {
public string Type { get; set; }
public List<T> Items { get; set; }
public T Item { get; set; }
}
}