298 lines
9.1 KiB
C#
298 lines
9.1 KiB
C#
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; }
|
||
}
|
||
} |