#if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using System.Linq; using Continentis.MainGame.Base; using Sirenix.OdinInspector; using SLSFramework.General; using UnityEditor; using UnityEngine; namespace Continentis.MainGame.Card { // ========================================================================= // CardDataEditor // // 注意:此类已不再使用 [CustomEditor] 注解。 // Odin Inspector 会自动接管 CardData(ScriptableObject)的 Inspector 渲染, // 无需任何自定义 Editor 类。本文件现主要用于承载 CardData 的编辑器扩展分部类。 // 如需将来添加 OdinEditorWindow 工具或其他编辑器工具类,可在此文件中继续扩展。 // ========================================================================= internal static class CardDataEditorPlaceholder { // 保留此类以维持文件存在意义,可在此添加未来的编辑器工具方法。 } // ========================================================================= // partial class CardData — 编辑器专属扩展 // // 包含: // 1. Odin ValueDropdown 数据提供方法 // 2. OnValueChanged 回调(自动填充 modName / categoryName / 文本Key) // 3. DrawCardSpriteField() — 16:9 自定义预览绘制器 // 4. PasteDefaultAttributes() — 粘贴默认属性模板按钮 // 5. OpenCardPreviewer() — 打开 CardPreviewer 工具窗口按钮 // 6. 内部共享辅助方法(AssetDatabase 查询) // ========================================================================= public partial class CardData { // ── 1. 卡牌逻辑类选择器 ─────────────────────────────────────────────── // 替代旧 CardDataEditor 中的 DrawSearchableTypeSelector() // 配合 CardData.cs 中字段上的 [ValueDropdown("@CardData.GetCardLogicDropdownItems()")] /// /// 为 [ValueDropdown] 提供所有 CardLogicBase 子类的层级下拉项。 /// 路径格式为 "ModName/Category/ClassName",与旧 TypeSelectorWindow 分组逻辑一致。 /// 此方法为 static,故在 [ValueDropdown] 中使用 @ 表达式语法引用。 /// public static IEnumerable> GetCardLogicDropdownItems() { const string namespacePrefix = "Continentis.Mods"; const string namespaceToRemove = "Cards"; var types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => { try { return a.GetTypes(); } catch { return Type.EmptyTypes; } }) .Where(t => typeof(CardLogicBase).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface && t != typeof(CardLogicBase)); foreach (var type in types.OrderBy(t => t.FullName)) { string path; if (type.Namespace != null && type.Namespace.StartsWith(namespacePrefix)) { // 去掉公共前缀,拆分剩余命名空间段落 var segments = type.Namespace .Substring(namespacePrefix.Length) .Split('.') .Where(s => !string.IsNullOrEmpty(s) && s != namespaceToRemove) .ToList(); path = segments.Count > 0 ? string.Join("/", segments) + "/" + type.Name : type.Name; } else { path = "Uncategorized/" + type.Name; } yield return new ValueDropdownItem(path, type.Name); } } /// /// 当 className 通过下拉菜单改变后,自动填充 modName / categoryName / displayName / functionText。 /// 等价于旧 CardDataEditor 中 DrawSearchableTypeSelector 的 onTypeSelected 回调。 /// 被 CardData.cs 中字段上的 [OnValueChanged("OnCardLogicClassSelected")] 触发。 /// private void OnCardLogicClassSelected() { if (string.IsNullOrEmpty(className)) return; // 根据 className 反查对应 Type(仅首个匹配) var selectedType = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => { try { return a.GetTypes(); } catch { return Type.EmptyTypes; } }) .FirstOrDefault(t => t.Name == className && typeof(CardLogicBase).IsAssignableFrom(t) && !t.IsAbstract); if (selectedType?.Namespace == null) return; const string prefix = "Continentis.Mods."; var ns = selectedType.Namespace; if (!ns.StartsWith(prefix)) return; // 解析 Mod 名与分类名 // 例:ns = "Continentis.Mods.Basic.Cards.Assassin" // → afterPrefix = "Basic.Cards.Assassin" // → parts = ["Basic", "Cards", "Assassin"] var afterPrefix = ns.Substring(prefix.Length); var parts = afterPrefix.Split('.'); var resolvedMod = parts.Length > 0 ? parts[0] : string.Empty; // 去掉 "{ModName}.Cards" 后的剩余部分即为 categoryName var cardsSegment = resolvedMod + ".Cards"; var resolvedCategory = ns.Contains(cardsSegment) ? ns.Replace(prefix + cardsSegment, "").TrimStart('.') : string.Empty; modName = resolvedMod; categoryName = resolvedCategory; displayName = $"Card_{resolvedMod}_{className}_DisplayName"; functionText = $"Card_{resolvedMod}_{className}_FunctionText"; descriptionText = $"Card_{resolvedMod}_{className}_Description"; EditorUtility.SetDirty(this); Debug.Log($"[CardData] 已自动填充 → modName: {modName}, categoryName: {categoryName}, " + $"className: {className}, displayName: {displayName}, functionText: {functionText}"); } // ── 2. 关键词下拉 ───────────────────────────────────────────────────── // 从所有 EditorBaseCollection 的 cardKeywords 字典聚合 Key 列表 private IEnumerable> GetAvailableKeywords() { return EditorBaseCollection.GetCardKeywordsDropdown(); } // ── 3. 意图图标下拉 ─────────────────────────────────────────────────── // 从所有 EditorBaseCollection 的 intentionIcons 字典聚合 Key 列表 private IEnumerable> GetAvailableIntentionIcons() { var seen = new HashSet(StringComparer.Ordinal); foreach (var coll in EditorBaseCollection.GetAllCollections()) { if (coll.intentionIcons == null) continue; foreach (var key in coll.intentionIcons.Keys) if (seen.Add(key)) yield return new ValueDropdownItem(key, key); } } // ── 4. intentionValueNames 候选(来自 variableAttributes 的 Key)──────── // 替代旧 DrawListWithLocalSelector(intentionValueNamesProp, "variableAttributes") private IEnumerable> GetVariableAttributeKeys() { if (variableAttributes == null) yield break; foreach (var key in variableAttributes.Keys) yield return new ValueDropdownItem(key, key); } // ── 5. References 列表的资产名称选择器 ────────────────────────────────── // 替代旧 DrawCharacterListGUI(prop) private IEnumerable> GetAvailablePrefabs() { return GetAssetNameDropdown("t:Prefab"); } private IEnumerable> GetAvailableCardData() { return GetAssetNameDropdown("t:CardData"); } private IEnumerable> GetAvailableCharacterData() { return GetAssetNameDropdown("t:CharacterData"); } [BoxGroup("Fundamental")] [PropertyOrder(6)] [Button("📄 打开逻辑脚本", ButtonSizes.Medium)] [GUIColor(1f, 0.92f, 0.6f)] [ShowIf("@!string.IsNullOrEmpty(className)")] private void OpenCardLogicScript() { if (string.IsNullOrEmpty(className)) { Debug.LogWarning("[CardData] 尚未选择逻辑类,无法打开对应脚本。"); return; } // 根据 modName / categoryName / className 拼接可能的文件路径 // 路径格式: Assets/Mods/{modName}/Cards/Scripts/{categoryName}/{className}.cs // | Assets/Mods/{modName}/Cards/Scripts/{className}.cs (无分类时) var candidatePaths = new List(); if (!string.IsNullOrEmpty(categoryName)) candidatePaths.Add($"Assets/Mods/{modName}/Cards/Scripts/{categoryName}/{className}.cs"); candidatePaths.Add($"Assets/Mods/{modName}/Cards/Scripts/{className}.cs"); // 在全项目中也搜一遍(应对目录结构不规范的情况) string[] guids = AssetDatabase.FindAssets($"{className} t:MonoScript"); foreach (string guid in guids) { string p = AssetDatabase.GUIDToAssetPath(guid); if (Path.GetFileNameWithoutExtension(p) == className) candidatePaths.Add(p); } foreach (string path in candidatePaths) { var script = AssetDatabase.LoadAssetAtPath(path); if (script != null) { AssetDatabase.OpenAsset(script); EditorGUIUtility.PingObject(script); Debug.Log($"[CardData] 已打开逻辑脚本:{path}"); return; } } Debug.LogError($"[CardData] 未找到逻辑类 '{className}' 对应的脚本文件。\n" + $"已搜索路径:\n{string.Join("\n", candidatePaths)}"); } [BoxGroup("Display")] [PropertyOrder(11)] [Button("✨ 预览卡牌效果", ButtonSizes.Medium)] [GUIColor(0.5f, 0.8f, 1f)] private void OpenCardPreviewer() { CardPreviewer.OpenWith(this); } [BoxGroup("Data/属性/工具")] [PropertyOrder(35)] [Button("粘贴默认属性", ButtonSizes.Medium)] [GUIColor(0.7f, 1f, 0.7f)] private void PasteDefaultAttributes() { var targetCollections = new List(); var guids = AssetDatabase.FindAssets("t:CardAttributesDefaultCollection"); if (guids.Length == 0) { Debug.LogWarning("[CardData] 未找到任何 CardAttributesDefaultCollection 资产。"); return; } if (guids.Length > 1) Debug.Log("[CardData] 找到多个 CardAttributesDefaultCollection,将合并后导入。"); foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); var directory = Path.GetDirectoryName(path)?.Replace('\\', '/'); var collection = AssetDatabase.LoadAssetAtPath(path); if (collection == null) continue; // 只收集位于 Assets/Mods/{ModName}/Cards/DefaultCollections/ 路径下的资产 if (!string.IsNullOrEmpty(directory) && directory.StartsWith("Assets/Mods/") && directory.EndsWith("/Cards/DefaultCollections")) { targetCollections.Add(collection); Debug.Log($"[CardData] 已加载默认集合:{path}"); } } if (targetCollections.Count == 0) { Debug.LogWarning("[CardData] 未在 Assets/Mods/{ModName}/Cards/DefaultCollections/ " + "路径下找到有效的 CardAttributesDefaultCollection 资产。"); return; } // 合并各 Collection 中的数据 var merged_variable = new Dictionary(); var merged_original = new Dictionary(); // Collection 端字段名仍为 endowingCurrentAttributes(CardData 端已用 [FormerlySerializedAs] 改名) var merged_runtime = new Dictionary(); foreach (var collection in targetCollections) { collection.variableAttributes?.PasteDictionary(merged_variable); collection.originalAttributes?.PasteDictionary(merged_original); collection.endowingCurrentAttributes?.PasteDictionary(merged_runtime); } // 写入当前 CardData merged_variable.PasteDictionary(variableAttributes); merged_original.PasteDictionary(originalAttributes); merged_runtime.PasteDictionary(runtimeCurrentAttributes); EditorUtility.SetDirty(this); Debug.Log($"[CardData] 已将默认属性粘贴至 '{name}'。"); } // ── 内部共享辅助方法 ───────────────────────────────────────────────── /// /// 通过 AssetDatabase 搜索特定类型/标签的资产,将文件名(无扩展名)作为下拉项返回。 /// 用于 References 列表的字符串引用选择。 /// private static IEnumerable> GetAssetNameDropdown(string searchFilter) { var guids = AssetDatabase.FindAssets(searchFilter); foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); var name = Path.GetFileNameWithoutExtension(path); yield return new ValueDropdownItem(name, name); } } } } #endif