Files
zeling_v2/Assets/_Game/Scripts/Editor/Modules/QuestValidationResultWindow.cs
Joywayer 6eaa83dc71 feat: Round 48 narrative systems improvements
- QuestSO: Add ValidateBranchCycles() DFS detection for branches[].nextQuest loop
- QuestSO: Mark three legacy prerequisite fields with v2.0 removal warning in Tooltip
- IQuestManager: Add QuestLockReason enum + QuestLockInfo struct (strongly-typed lock info)
- IQuestManager: Add GetQuestLockInfo() method to interface; GetQuestLockReason() now delegates to it
- IQuestEventSource: Add OnQuestStateChanged(questId, oldState, newState) unified event
- QuestManager: Implement GetQuestLockInfo(); fire OnQuestStateChanged on all state transitions
- DialogueManager: Add one-frame yield in HandleChoices before ShowChoices (skip-debounce fix)
- DialogueManager: Increment _playbackId in ForceEnd() to invalidate residual choice callbacks
- DialogueSequenceSO: Add UNITY_EDITOR debug log in TryGetActiveVariant on variant match
- WorldStateRegistry: Add OnBatchStateChanged event + BatchMark() batch-write API
- DialogueModule: List badge shows warning indicator for unconditional-shadowing variants
- DialogueModule: BuildVariantsCard shows logic mode (AND/OR) alongside flag conditions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 00:05:15 +08:00

172 lines
7.6 KiB
C#
Raw 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.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace BaseGames.Editor.Modules
{
/// <summary>
/// 批量验证结果窗口:以可滚动列表展示各项配置问题,
/// 每条记录附带"选中"按钮点击后高亮并选中对应资产EditorGUIUtility.PingObject
/// 支持 Error/Warning Tab 切换与文本搜索过滤。
/// 由各验证模块QuestModule、DialogueModule、NpcModule在检测到问题时弹出。
/// </summary>
internal class QuestValidationResultWindow : EditorWindow
{
internal struct Issue
{
public string message;
public bool isError;
public UnityEngine.Object asset; // null = 无对应资产(如孤儿触发器)
}
private List<Issue> _issues;
private int _errorCount;
private int _warnCount;
private int _totalItems;
private string _itemLabel = "资产";
private Vector2 _scroll;
// ── 过滤状态 ─────────────────────────────────────────────────────────
private enum TabMode { All, ErrorsOnly, WarnsOnly }
private TabMode _tab = TabMode.All;
private string _filter = "";
private static readonly GUIStyle s_tabActive = null; // 延迟初始化
private static readonly GUIStyle s_tabInactive = null;
// ── 打开入口 ──────────────────────────────────────────────────────────
internal static void Show(List<Issue> issues, int errorCount, int warnCount, int totalItems, string windowTitle = "批量验证结果", string itemLabel = "资产")
{
var win = GetWindow<QuestValidationResultWindow>(true, windowTitle, true);
win._issues = issues;
win._errorCount = errorCount;
win._warnCount = warnCount;
win._totalItems = totalItems;
win._itemLabel = itemLabel;
win._tab = TabMode.All;
win._filter = "";
win.minSize = new Vector2(560, 380);
win.Show();
}
// ── 绘制 ──────────────────────────────────────────────────────────────
private void OnGUI()
{
if (_issues == null) { EditorGUILayout.LabelField("无数据。"); return; }
bool clean = _errorCount == 0 && _warnCount == 0;
// ── 标题摘要 ──
EditorGUILayout.Space(6);
var summaryStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 12 };
if (clean) summaryStyle.normal.textColor = new Color(0.2f, 0.75f, 0.2f);
string prefix = clean ? "✅ " : (_errorCount > 0 ? "❌ " : "⚠ ");
string summary = $"{prefix}验证完成:{_totalItems} 个{_itemLabel} · {_errorCount} 个错误 · {_warnCount} 个警告";
EditorGUILayout.LabelField(summary, summaryStyle);
EditorGUILayout.Space(4);
if (clean)
{
EditorGUILayout.LabelField($"所有 {_itemLabel} 配置均合法!", EditorStyles.centeredGreyMiniLabel);
return;
}
// ── 分隔线 ──
var divRect = EditorGUILayout.GetControlRect(false, 1);
EditorGUI.DrawRect(divRect, new Color(0.35f, 0.35f, 0.35f));
EditorGUILayout.Space(4);
// ── Tab 切换 + 搜索框 ──
EditorGUILayout.BeginHorizontal();
DrawTab("全部", TabMode.All, _issues.Count);
DrawTab("错误", TabMode.ErrorsOnly, _errorCount);
DrawTab("警告", TabMode.WarnsOnly, _warnCount);
GUILayout.FlexibleSpace();
GUILayout.Label("🔍", GUILayout.Width(18));
_filter = EditorGUILayout.TextField(_filter, GUILayout.Width(180));
if (GUILayout.Button("×", GUILayout.Width(22))) _filter = "";
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(4);
// ── 问题列表 ──
_scroll = EditorGUILayout.BeginScrollView(_scroll);
int shown = 0;
foreach (var issue in _issues)
{
if (!MatchesFilter(issue)) continue;
shown++;
DrawIssueRow(issue);
EditorGUILayout.Space(2);
}
if (shown == 0)
EditorGUILayout.LabelField("(当前过滤条件下无结果)", EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.EndScrollView();
// ── 底部状态栏 ──
EditorGUILayout.Space(2);
var statusRect = EditorGUILayout.GetControlRect(false, 1);
EditorGUI.DrawRect(statusRect, new Color(0.3f, 0.3f, 0.3f));
EditorGUILayout.LabelField(
$"显示 {shown} / {_issues.Count} 条",
EditorStyles.centeredGreyMiniLabel);
}
// ── 辅助方法 ─────────────────────────────────────────────────────────
private bool MatchesFilter(Issue issue)
{
if (_tab == TabMode.ErrorsOnly && !issue.isError) return false;
if (_tab == TabMode.WarnsOnly && issue.isError) return false;
if (!string.IsNullOrEmpty(_filter))
{
bool msgMatch = issue.message.Contains(_filter, System.StringComparison.OrdinalIgnoreCase);
bool assetMatch = issue.asset != null &&
issue.asset.name.Contains(_filter, System.StringComparison.OrdinalIgnoreCase);
if (!msgMatch && !assetMatch) return false;
}
return true;
}
private void DrawTab(string label, TabMode mode, int count)
{
bool active = _tab == mode;
var style = active
? new GUIStyle(EditorStyles.miniButtonMid) { fontStyle = FontStyle.Bold }
: EditorStyles.miniButtonMid;
if (active) style.normal.textColor = new Color(0.4f, 0.8f, 1f);
string text = $"{label} ({count})";
if (GUILayout.Button(text, style, GUILayout.MinWidth(72)))
_tab = mode;
}
private static void DrawIssueRow(Issue issue)
{
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
// 图标
var iconContent = issue.isError
? EditorGUIUtility.IconContent("console.erroricon.sml")
: EditorGUIUtility.IconContent("console.warnicon.sml");
GUILayout.Label(iconContent, GUILayout.Width(20), GUILayout.Height(18));
// 消息文字
EditorGUILayout.LabelField(issue.message, EditorStyles.wordWrappedLabel);
// 定位按钮(有资产引用时显示)
if (issue.asset != null)
{
if (GUILayout.Button("选中", GUILayout.Width(40), GUILayout.Height(18)))
{
EditorGUIUtility.PingObject(issue.asset);
Selection.activeObject = issue.asset;
}
}
EditorGUILayout.EndHorizontal();
}
}
}