using System; using System.Collections.Generic; using UnityEngine; namespace AibisDream.FixSystem { [Serializable] public sealed class PunchTapeDefinition { [SerializeField] private string id; [SerializeField] private string displayNameParam; [SerializeField] private string dialogNode; [SerializeField] private string memoryResourceKey; [SerializeField] private string visibilityCode; [SerializeField] private string contentId; [SerializeField] private string timecode; public string Id => id; public string DisplayNameParam => displayNameParam; public string DialogNode => dialogNode; public string MemoryResourceKey => memoryResourceKey; public string VisibilityCode => visibilityCode; public string ContentId => contentId; public string Timecode => timecode; public bool TryValidate(out string error) { if (string.IsNullOrWhiteSpace(id)) { error = "ID 不能为空。"; return false; } if (string.IsNullOrWhiteSpace(displayNameParam) || !displayNameParam.StartsWith("l10n.", StringComparison.Ordinal)) { error = $"'{id}' 的名称必须配置为 l10n.* 参数键。"; return false; } if (string.IsNullOrWhiteSpace(dialogNode) || string.IsNullOrWhiteSpace(memoryResourceKey) || string.IsNullOrWhiteSpace(visibilityCode) || string.IsNullOrWhiteSpace(contentId) || string.IsNullOrWhiteSpace(timecode)) { error = $"'{id}' 存在未填写的流程或显示字段。"; return false; } error = null; return true; } #if UNITY_EDITOR public void ConfigureForEditor(string definitionId, string localizedNameParam, string yarnDialogNode, string resourceKey, string visibility, string cid, string timestamp) { id = definitionId; displayNameParam = localizedNameParam; dialogNode = yarnDialogNode; memoryResourceKey = resourceKey; visibilityCode = visibility; contentId = cid; timecode = timestamp; } #endif } [CreateAssetMenu(fileName = "PunchTapeCatalog", menuName = AibisAssetMenus.MemoryPunchTapeCatalog)] public sealed class PunchTapeCatalog : ScriptableObject { [SerializeField] private List definitions = new(); private Dictionary definitionById; public IReadOnlyList Definitions => definitions; public bool TryGetDefinition(string definitionId, out PunchTapeDefinition definition) { EnsureLookup(); if (string.IsNullOrWhiteSpace(definitionId)) { definition = null; return false; } return definitionById.TryGetValue(definitionId, out definition); } public List GetValidationErrors() { var errors = new List(); var ids = new HashSet(StringComparer.Ordinal); if (definitions == null || definitions.Count == 0) { errors.Add("Catalog 中没有打孔带定义。"); return errors; } for (var i = 0; i < definitions.Count; i++) { var definition = definitions[i]; if (definition == null) { errors.Add($"definitions[{i}] 为空。"); continue; } if (!definition.TryValidate(out var error)) errors.Add(error); if (!string.IsNullOrWhiteSpace(definition.Id) && !ids.Add(definition.Id)) errors.Add($"存在重复 ID:'{definition.Id}'。"); } return errors; } private void EnsureLookup() { if (definitionById != null) return; definitionById = new Dictionary(StringComparer.Ordinal); if (definitions == null) return; foreach (var definition in definitions) { if (definition == null || string.IsNullOrWhiteSpace(definition.Id) || definitionById.ContainsKey(definition.Id)) continue; definitionById.Add(definition.Id, definition); } } private void OnEnable() { definitionById = null; } #if UNITY_EDITOR private void OnValidate() { definitionById = null; } public void SetDefinitionsForEditor(IEnumerable newDefinitions) { definitions = newDefinitions == null ? new List() : new List(newDefinitions); definitionById = null; } #endif } }