| | |
| | | private Vector2 scrollPosition; |
| | | private Vector2 detailScrollPosition; |
| | | private string searchText = ""; |
| | | private string detailSearchText = ""; // 详情搜索文本 |
| | | private string lastDetailSearchText = ""; // 上次的详情搜索文本,用于检测变化 |
| | | private string selectedGuid = ""; |
| | | private List<string> guidList = new List<string>(); |
| | | private int selectedGuidIndex = 0; |
| | | private List<PackageInfo> filteredPackages = new List<PackageInfo>(); |
| | | private PackageInfo selectedPackage = null; |
| | | private PackageInfo lastSelectedPackage = null; // 上次选中的包,用于检测包切换 |
| | | |
| | | // 高亮相关 |
| | | private PackageInfo highlightedPackage = null; |
| | | private double highlightStartTime = 0; |
| | | private const double highlightDuration = 5.0; // 高亮持续5秒 |
| | | private int selectedPackageIndex = -1; // 记录选中包在列表中的索引,用于滚动 |
| | | |
| | | // 搜索缓存 |
| | | private HashSet<string> matchedFieldNames = new HashSet<string>(); // 缓存匹配的字段名 |
| | | private Dictionary<string, bool> fieldHasMatchedChild = new Dictionary<string, bool>(); // 缓存字段是否有匹配的子元素 |
| | | |
| | | #endregion |
| | | |
| | |
| | | |
| | | if (GUILayout.Button("刷新", EditorStyles.toolbarButton, GUILayout.Width(60))) |
| | | { |
| | | // 刷新前清空搜索缓存,避免卡死 |
| | | matchedFieldNames.Clear(); |
| | | fieldHasMatchedChild.Clear(); |
| | | lastSelectedPackage = null; |
| | | lastDetailSearchText = ""; |
| | | |
| | | RefreshGuidList(); |
| | | FilterPackages(); |
| | | } |
| | |
| | | { |
| | | searchText = newSearchText; |
| | | FilterPackages(); |
| | | |
| | | // 搜索后自动选中第一个结果并高亮 |
| | | if (filteredPackages.Count > 0) |
| | | { |
| | | selectedPackage = filteredPackages[0]; |
| | | selectedPackageIndex = 0; |
| | | StartHighlight(selectedPackage); |
| | | ScrollToSelectedPackage(); |
| | | } |
| | | } |
| | | |
| | | if (GUILayout.Button("清空", GUILayout.Width(60))) |
| | |
| | | bool isExecuted = executedPackUIDs.ContainsKey(selectedGuid) && |
| | | executedPackUIDs[selectedGuid].Contains(packInfo.packUID); |
| | | |
| | | // 判断是否是高亮状态 |
| | | bool isHighlighted = IsPackageHighlighted(packInfo); |
| | | |
| | | // 设置背景颜色 |
| | | Color originalBgColor = GUI.backgroundColor; |
| | | if (isExecuting) |
| | | if (isHighlighted) |
| | | { |
| | | // 高亮状态 - 使用绿色闪烁效果 |
| | | float pulse = Mathf.PingPong((float)EditorApplication.timeSinceStartup * 2f, 1f); |
| | | GUI.backgroundColor = Color.Lerp(new Color(0.3f, 0.8f, 0.3f), new Color(0.5f, 1f, 0.5f), pulse); |
| | | } |
| | | else if (isExecuting) |
| | | { |
| | | GUI.backgroundColor = new Color(0.3f, 0.3f, 0.3f); // 深黑色(正在执行) |
| | | } |
| | |
| | | if (GUILayout.Button(packInfo.displayName, style)) |
| | | { |
| | | selectedPackage = packInfo; |
| | | selectedPackageIndex = filteredPackages.IndexOf(packInfo); |
| | | } |
| | | |
| | | EditorGUILayout.EndHorizontal(); |
| | |
| | | } |
| | | else |
| | | { |
| | | // 详情搜索栏 |
| | | EditorGUILayout.BeginHorizontal(); |
| | | GUILayout.Label("搜索字段:", GUILayout.Width(70)); |
| | | detailSearchText = EditorGUILayout.TextField(detailSearchText); |
| | | if (GUILayout.Button("清空", GUILayout.Width(50))) |
| | | { |
| | | detailSearchText = ""; |
| | | } |
| | | EditorGUILayout.EndHorizontal(); |
| | | |
| | | // 检测选中包或搜索文本变化,更新缓存 |
| | | bool packageChanged = selectedPackage != lastSelectedPackage; |
| | | bool searchTextChanged = detailSearchText != lastDetailSearchText; |
| | | |
| | | if (packageChanged || searchTextChanged) |
| | | { |
| | | lastDetailSearchText = detailSearchText; |
| | | lastSelectedPackage = selectedPackage; |
| | | UpdateDetailSearchCache(); |
| | | } |
| | | |
| | | if (string.IsNullOrEmpty(detailSearchText)) |
| | | { |
| | | GUILayout.Label("在此包的字段中搜索(字段名或值)", EditorStyles.miniLabel); |
| | | } |
| | | else |
| | | { |
| | | GUILayout.Label($"找到 {matchedFieldNames.Count} 个匹配的字段(已自动展开并高亮)", EditorStyles.miniLabel); |
| | | } |
| | | EditorGUILayout.Space(5); |
| | | |
| | | detailScrollPosition = EditorGUILayout.BeginScrollView(detailScrollPosition, GUI.skin.box); |
| | | |
| | | // 标题 |
| | | GUILayout.Label(selectedPackage.displayName, EditorStyles.boldLabel); |
| | | EditorGUILayout.Space(5); |
| | | |
| | | // 基本信息 |
| | | DrawDetailField("包类型", selectedPackage.typeName); |
| | | DrawDetailField("UID", selectedPackage.packUID.ToString()); |
| | | DrawDetailField("Cmd", selectedPackage.package.cmd.ToString()); |
| | | // 基本信息 - 支持搜索高亮 |
| | | string searchLower = detailSearchText.ToLower(); |
| | | bool typeMatched = !string.IsNullOrEmpty(detailSearchText) && selectedPackage.typeName.ToLower().Contains(searchLower); |
| | | bool uidMatched = !string.IsNullOrEmpty(detailSearchText) && selectedPackage.packUID.ToString().Contains(searchLower); |
| | | bool cmdMatched = !string.IsNullOrEmpty(detailSearchText) && selectedPackage.package.cmd.ToString().Contains(searchLower); |
| | | |
| | | DrawDetailFieldWithHighlight("包类型", selectedPackage.typeName, typeMatched); |
| | | DrawDetailFieldWithHighlight("UID", selectedPackage.packUID.ToString(), uidMatched); |
| | | DrawDetailFieldWithHighlight("Cmd", selectedPackage.package.cmd.ToString(), cmdMatched); |
| | | EditorGUILayout.Space(10); |
| | | |
| | | // 所有字段 |
| | | GUILayout.Label("字段列表:", EditorStyles.boldLabel); |
| | | |
| | | // 根据搜索文本过滤字段 |
| | | foreach (var field in selectedPackage.fields) |
| | | { |
| | | DrawDetailFieldWithExpand(field.Key, field.Value, selectedPackage); |
| | | if (ShouldShowDetailField(field.Key, field.Value, selectedPackage)) |
| | | { |
| | | DrawDetailFieldWithExpand(field.Key, field.Value, selectedPackage); |
| | | } |
| | | } |
| | | |
| | | EditorGUILayout.EndScrollView(); |
| | |
| | | EditorGUILayout.EndHorizontal(); |
| | | } |
| | | |
| | | private void DrawDetailFieldWithHighlight(string fieldName, string value, bool isMatched) |
| | | { |
| | | if (isMatched) |
| | | { |
| | | Color originalBgColor = GUI.backgroundColor; |
| | | GUI.backgroundColor = new Color(1f, 1f, 0.5f); // 淡黄色背景 |
| | | EditorGUILayout.BeginHorizontal(GUI.skin.box); |
| | | GUI.backgroundColor = originalBgColor; |
| | | |
| | | GUIStyle labelStyle = new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }; |
| | | GUILayout.Label(fieldName + ":", labelStyle, GUILayout.Width(150)); |
| | | EditorGUILayout.SelectableLabel(value, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | |
| | | EditorGUILayout.EndHorizontal(); |
| | | } |
| | | else |
| | | { |
| | | DrawDetailField(fieldName, value); |
| | | } |
| | | } |
| | | |
| | | private void DrawDetailFieldWithExpand(string fieldName, string value, PackageInfo packInfo) |
| | | { |
| | | if (!packInfo.fieldDetails.ContainsKey(fieldName)) |
| | |
| | | } |
| | | |
| | | var detail = packInfo.fieldDetails[fieldName]; |
| | | |
| | | // 使用缓存检查是否匹配 |
| | | bool isMatched = matchedFieldNames.Contains(fieldName); |
| | | |
| | | // 使用缓存检查子元素是否匹配 |
| | | bool hasMatchedChild = false; |
| | | if (!string.IsNullOrEmpty(detailSearchText)) |
| | | { |
| | | fieldHasMatchedChild.TryGetValue(fieldName, out hasMatchedChild); |
| | | } |
| | | |
| | | // 如果自身匹配或子元素匹配,自动展开 |
| | | if ((isMatched || hasMatchedChild) && !string.IsNullOrEmpty(detailSearchText)) |
| | | { |
| | | detail.isExpanded = true; |
| | | } |
| | | |
| | | // 判断是否是复杂对象(非基本类型、非字符串、非枚举) |
| | | bool isComplexObject = detail.value != null && |
| | |
| | | // 如果是数组、列表或复杂对象,显示展开按钮 |
| | | if (detail.isArray || detail.isList || isComplexObject) |
| | | { |
| | | EditorGUILayout.BeginHorizontal(); |
| | | // 如果匹配或有匹配的子元素,添加背景颜色 |
| | | Color originalBgColor = GUI.backgroundColor; |
| | | if (isMatched || hasMatchedChild) |
| | | { |
| | | GUI.backgroundColor = new Color(1f, 1f, 0.5f); // 淡黄色背景 |
| | | } |
| | | |
| | | EditorGUILayout.BeginHorizontal((isMatched || hasMatchedChild) ? GUI.skin.box : GUIStyle.none); |
| | | GUI.backgroundColor = originalBgColor; |
| | | |
| | | // 展开按钮 |
| | | string foldoutLabel = detail.isExpanded ? "▼" : "▶"; |
| | |
| | | detail.isExpanded = !detail.isExpanded; |
| | | } |
| | | |
| | | GUILayout.Label(fieldName + ":", GUILayout.Width(130)); |
| | | // 字段名样式 |
| | | GUIStyle labelStyle = (isMatched || hasMatchedChild) ? new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold } : GUI.skin.label; |
| | | GUILayout.Label(fieldName + ":", labelStyle, GUILayout.Width(130)); |
| | | EditorGUILayout.SelectableLabel(value, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | |
| | | EditorGUILayout.EndHorizontal(); |
| | |
| | | } |
| | | else |
| | | { |
| | | DrawDetailField(fieldName, value); |
| | | // 简单字段也添加高亮 |
| | | if (isMatched) |
| | | { |
| | | Color originalBgColor = GUI.backgroundColor; |
| | | GUI.backgroundColor = new Color(1f, 1f, 0.5f); |
| | | EditorGUILayout.BeginHorizontal(GUI.skin.box); |
| | | GUI.backgroundColor = originalBgColor; |
| | | |
| | | GUIStyle labelStyle = new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }; |
| | | GUILayout.Label(fieldName + ":", labelStyle, GUILayout.Width(150)); |
| | | EditorGUILayout.SelectableLabel(value, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | |
| | | EditorGUILayout.EndHorizontal(); |
| | | } |
| | | else |
| | | { |
| | | DrawDetailField(fieldName, value); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | var type = element.GetType(); |
| | | |
| | | // 检查元素是否匹配搜索 |
| | | bool isElementMatched = false; |
| | | if (!string.IsNullOrEmpty(detailSearchText)) |
| | | { |
| | | HashSet<object> visited = new HashSet<object>(); |
| | | isElementMatched = RecursiveSearchInObject(element, detailSearchText.ToLower(), visited); |
| | | } |
| | | |
| | | // 检查是否是 GameNetPackBasic 类型,并判断是否处于执行状态 |
| | | bool isPackageElement = element is GameNetPackBasic; |
| | | bool isHighlightedPackage = false; |
| | | bool isExecutingPackage = false; |
| | | bool isExecutedPackage = false; |
| | | |
| | | if (isPackageElement) |
| | | { |
| | | GameNetPackBasic pack = element as GameNetPackBasic; |
| | | ulong packUID = pack.packUID; |
| | | |
| | | // 检查高亮状态 |
| | | if (highlightedPackage != null && highlightedPackage.packUID == packUID) |
| | | { |
| | | double elapsed = EditorApplication.timeSinceStartup - highlightStartTime; |
| | | isHighlightedPackage = elapsed < highlightDuration; |
| | | } |
| | | |
| | | // 检查执行状态 |
| | | isExecutingPackage = currentExecutingPackUID.ContainsKey(selectedGuid) && |
| | | currentExecutingPackUID[selectedGuid] == packUID; |
| | | isExecutedPackage = executedPackUIDs.ContainsKey(selectedGuid) && |
| | | executedPackUIDs[selectedGuid].Contains(packUID); |
| | | } |
| | | |
| | | // 如果是基本类型,直接显示 |
| | | if (type.IsPrimitive || type == typeof(string) || type.IsEnum) |
| | | { |
| | | EditorGUILayout.BeginHorizontal(); |
| | | EditorGUILayout.PrefixLabel($"[{index}]"); |
| | | EditorGUILayout.SelectableLabel(element.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | EditorGUILayout.EndHorizontal(); |
| | | // 如果匹配,添加高亮 |
| | | if (isElementMatched) |
| | | { |
| | | Color originalBgColor = GUI.backgroundColor; |
| | | GUI.backgroundColor = new Color(1f, 1f, 0.5f); |
| | | EditorGUILayout.BeginHorizontal(GUI.skin.box); |
| | | GUI.backgroundColor = originalBgColor; |
| | | |
| | | GUIStyle labelStyle = new GUIStyle(EditorStyles.label) { fontStyle = FontStyle.Bold }; |
| | | EditorGUILayout.PrefixLabel($"[{index}]", labelStyle, labelStyle); |
| | | EditorGUILayout.SelectableLabel(element.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | |
| | | EditorGUILayout.EndHorizontal(); |
| | | } |
| | | else |
| | | { |
| | | EditorGUILayout.BeginHorizontal(); |
| | | EditorGUILayout.PrefixLabel($"[{index}]"); |
| | | EditorGUILayout.SelectableLabel(element.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | EditorGUILayout.EndHorizontal(); |
| | | } |
| | | } |
| | | else |
| | | { |
| | |
| | | |
| | | var elemDetail = parentDetail?.elementDetails[index]; |
| | | |
| | | // 如果元素匹配搜索,自动展开 |
| | | if (isElementMatched && elemDetail != null) |
| | | { |
| | | elemDetail.isExpanded = true; |
| | | } |
| | | |
| | | // 为包元素设置背景颜色 |
| | | Color originalBgColor = GUI.backgroundColor; |
| | | if (isPackageElement) |
| | | { |
| | | if (isHighlightedPackage) |
| | | { |
| | | // 高亮状态 - 使用绿色闪烁效果 |
| | | float pulse = Mathf.PingPong((float)EditorApplication.timeSinceStartup * 2f, 1f); |
| | | GUI.backgroundColor = Color.Lerp(new Color(0.3f, 0.8f, 0.3f), new Color(0.5f, 1f, 0.5f), pulse); |
| | | } |
| | | else if (isExecutingPackage) |
| | | { |
| | | GUI.backgroundColor = new Color(0.3f, 0.3f, 0.3f); // 深黑色(正在执行) |
| | | } |
| | | else if (isExecutedPackage) |
| | | { |
| | | GUI.backgroundColor = new Color(0.6f, 0.6f, 0.6f); // 灰色(已执行) |
| | | } |
| | | else if (isElementMatched) |
| | | { |
| | | GUI.backgroundColor = new Color(1f, 1f, 0.5f); // 黄色(搜索匹配) |
| | | } |
| | | } |
| | | else if (isElementMatched) |
| | | { |
| | | GUI.backgroundColor = new Color(1f, 1f, 0.5f); // 黄色(搜索匹配) |
| | | } |
| | | |
| | | EditorGUILayout.BeginVertical(isPackageElement || isElementMatched ? GUI.skin.box : GUIStyle.none); |
| | | GUI.backgroundColor = originalBgColor; |
| | | |
| | | // 展开按钮 + 类型名 |
| | | GUIStyle labelStyle = EditorStyles.foldout; |
| | | if (isPackageElement) |
| | | { |
| | | if (isHighlightedPackage) |
| | | { |
| | | labelStyle = new GUIStyle(EditorStyles.foldout) { normal = { textColor = Color.green }, fontStyle = FontStyle.Bold }; |
| | | } |
| | | else if (isExecutingPackage) |
| | | { |
| | | labelStyle = new GUIStyle(EditorStyles.foldout) { normal = { textColor = Color.yellow }, fontStyle = FontStyle.Bold }; |
| | | } |
| | | else if (isExecutedPackage) |
| | | { |
| | | labelStyle = new GUIStyle(EditorStyles.foldout) { normal = { textColor = Color.gray } }; |
| | | } |
| | | else if (isElementMatched) |
| | | { |
| | | labelStyle = new GUIStyle(EditorStyles.foldout) { fontStyle = FontStyle.Bold }; |
| | | } |
| | | } |
| | | else if (isElementMatched) |
| | | { |
| | | labelStyle = new GUIStyle(EditorStyles.foldout) { fontStyle = FontStyle.Bold }; |
| | | } |
| | | |
| | | if (elemDetail != null) |
| | | { |
| | | string foldoutLabel = elemDetail.isExpanded ? "▼" : "▶"; |
| | | elemDetail.isExpanded = EditorGUILayout.Foldout(elemDetail.isExpanded, $"[{index}] {type.Name}", true); |
| | | GameNetPackBasic pack = element as GameNetPackBasic; |
| | | string displayText = isPackageElement ? $"[{index}] [{pack.packUID}] {type.Name}" : $"[{index}] {type.Name}"; |
| | | |
| | | // 特殊处理:HB427 技能包显示释放者和技能名 |
| | | if (element is HB427_tagSCUseSkill skillPack) |
| | | { |
| | | string casterName = GetObjectName(skillPack.ObjID); |
| | | string skillName = GetSkillName(skillPack.SkillID); |
| | | displayText += $" - {casterName} 使用 {skillName}"; |
| | | } |
| | | // 特殊处理:CustomHB426CombinePack 显示是否需要等待 |
| | | else if (element is CustomHB426CombinePack combinePack) |
| | | { |
| | | bool needWaiting = combinePack.NeedWaiting(); |
| | | displayText += needWaiting ? " [需要等待]" : " [无需等待]"; |
| | | } |
| | | |
| | | elemDetail.isExpanded = EditorGUILayout.Foldout(elemDetail.isExpanded, displayText, true, labelStyle); |
| | | } |
| | | else |
| | | { |
| | |
| | | EditorGUI.indentLevel++; |
| | | DrawObjectFields(element, elemDetail.fieldDetails); |
| | | EditorGUI.indentLevel--; |
| | | } |
| | | |
| | | if (isPackageElement || isElementMatched) |
| | | { |
| | | EditorGUILayout.EndVertical(); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | var type = value.GetType(); |
| | | |
| | | // 检查字段值是否匹配搜索 |
| | | bool isValueMatched = false; |
| | | if (!string.IsNullOrEmpty(detailSearchText)) |
| | | { |
| | | string searchLower = detailSearchText.ToLower(); |
| | | // 字段名匹配 |
| | | if (fieldName.ToLower().Contains(searchLower)) |
| | | isValueMatched = true; |
| | | // 字段值匹配 |
| | | else if (value.ToString().Contains(searchLower)) |
| | | isValueMatched = true; |
| | | } |
| | | |
| | | // 基本类型直接显示 |
| | | if (type.IsPrimitive || type == typeof(string) || type.IsEnum) |
| | | { |
| | | EditorGUILayout.BeginHorizontal(); |
| | | EditorGUILayout.PrefixLabel(fieldName); |
| | | EditorGUILayout.SelectableLabel(value.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | EditorGUILayout.EndHorizontal(); |
| | | // 获取显示值,如果是ObjID则追加对象名称 |
| | | string displayValue = value.ToString(); |
| | | if (fieldName == "ObjID" && uint.TryParse(displayValue, out uint objID)) |
| | | { |
| | | string objName = GetObjectName(objID); |
| | | displayValue = $"{displayValue} ({objName})"; |
| | | } |
| | | |
| | | if (isValueMatched) |
| | | { |
| | | // 高亮显示匹配的字段 |
| | | Color originalBgColor = GUI.backgroundColor; |
| | | GUI.backgroundColor = new Color(1f, 1f, 0.5f); // 淡黄色背景 |
| | | EditorGUILayout.BeginHorizontal(GUI.skin.box); |
| | | GUI.backgroundColor = originalBgColor; |
| | | |
| | | GUIStyle labelStyle = new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }; |
| | | GUILayout.Space(EditorGUI.indentLevel * 15); // 手动缩进 |
| | | GUILayout.Label(fieldName + ":", labelStyle, GUILayout.Width(120)); |
| | | EditorGUILayout.SelectableLabel(displayValue, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | |
| | | EditorGUILayout.EndHorizontal(); |
| | | } |
| | | else |
| | | { |
| | | EditorGUILayout.BeginHorizontal(); |
| | | GUILayout.Space(EditorGUI.indentLevel * 15); // 手动缩进 |
| | | GUILayout.Label(fieldName + ":", GUILayout.Width(120)); |
| | | EditorGUILayout.SelectableLabel(displayValue, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); |
| | | EditorGUILayout.EndHorizontal(); |
| | | } |
| | | } |
| | | // 数组类型 |
| | | else if (type.IsArray && fieldDetail.arrayValue != null) |
| | |
| | | |
| | | #endregion |
| | | |
| | | #region 详情搜索 |
| | | |
| | | /// <summary> |
| | | /// 更新详情搜索缓存(仅在搜索文本改变时调用) |
| | | /// </summary> |
| | | private void UpdateDetailSearchCache() |
| | | { |
| | | matchedFieldNames.Clear(); |
| | | fieldHasMatchedChild.Clear(); |
| | | |
| | | if (selectedPackage == null || string.IsNullOrEmpty(detailSearchText)) |
| | | return; |
| | | |
| | | string searchLower = detailSearchText.ToLower(); |
| | | |
| | | // 检查基本信息是否匹配(包类型、UID、Cmd) |
| | | bool basicInfoMatched = selectedPackage.typeName.ToLower().Contains(searchLower) || |
| | | selectedPackage.packUID.ToString().Contains(searchLower) || |
| | | selectedPackage.package.cmd.ToString().Contains(searchLower); |
| | | |
| | | foreach (var field in selectedPackage.fields) |
| | | { |
| | | string fieldName = field.Key; |
| | | string fieldValue = field.Value; |
| | | |
| | | // 检查字段名或字段值是否匹配 |
| | | bool fieldMatched = fieldName.ToLower().Contains(searchLower) || |
| | | fieldValue.ToLower().Contains(searchLower); |
| | | |
| | | if (fieldMatched) |
| | | { |
| | | matchedFieldNames.Add(fieldName); |
| | | } |
| | | |
| | | // 检查数组/列表的子元素是否匹配 |
| | | if (selectedPackage.fieldDetails.ContainsKey(fieldName)) |
| | | { |
| | | var detail = selectedPackage.fieldDetails[fieldName]; |
| | | if (detail.isArray || detail.isList) |
| | | { |
| | | bool hasMatch = CheckChildElementsMatch(detail, searchLower); |
| | | if (hasMatch) |
| | | { |
| | | fieldHasMatchedChild[fieldName] = true; |
| | | if (!fieldMatched) |
| | | { |
| | | matchedFieldNames.Add(fieldName); // 也算作匹配字段 |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 如果基本信息匹配但没有字段匹配,至少显示一个字段让用户知道有匹配 |
| | | if (basicInfoMatched && matchedFieldNames.Count == 0 && selectedPackage.fields.Count > 0) |
| | | { |
| | | // 添加第一个字段,这样至少会显示一些内容 |
| | | var firstField = selectedPackage.fields.First(); |
| | | matchedFieldNames.Add(firstField.Key); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查字段是否匹配搜索(用于高亮显示) |
| | | /// </summary> |
| | | private bool IsFieldMatched(string fieldName, string fieldValue, FieldDetail detail) |
| | | { |
| | | if (string.IsNullOrEmpty(detailSearchText)) |
| | | return false; |
| | | |
| | | string searchLower = detailSearchText.ToLower(); |
| | | |
| | | // 字段名匹配 |
| | | if (fieldName.ToLower().Contains(searchLower)) |
| | | return true; |
| | | |
| | | // 字段值匹配 |
| | | if (fieldValue.ToLower().Contains(searchLower)) |
| | | return true; |
| | | |
| | | return false; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查数组/列表中是否有匹配的子元素(用于缓存计算) |
| | | /// </summary> |
| | | private bool CheckChildElementsMatch(FieldDetail detail, string searchLower) |
| | | { |
| | | HashSet<object> visited = new HashSet<object>(); |
| | | |
| | | // 检查数组 |
| | | if (detail.isArray && detail.arrayValue != null) |
| | | { |
| | | foreach (var element in detail.arrayValue) |
| | | { |
| | | if (element != null) |
| | | { |
| | | // 特殊处理:如果是 GameNetPackBasic,优先检查 packUID |
| | | if (element is GameNetPackBasic pack && pack.packUID.ToString().Contains(searchLower)) |
| | | return true; |
| | | |
| | | if (RecursiveSearchInObject(element, searchLower, visited)) |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 检查列表 |
| | | if (detail.isList && detail.listValue != null) |
| | | { |
| | | foreach (var element in detail.listValue) |
| | | { |
| | | if (element != null) |
| | | { |
| | | // 特殊处理:如果是 GameNetPackBasic,优先检查 packUID |
| | | if (element is GameNetPackBasic pack && pack.packUID.ToString().Contains(searchLower)) |
| | | return true; |
| | | |
| | | if (RecursiveSearchInObject(element, searchLower, visited)) |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 判断字段是否应该显示(根据详情搜索文本) |
| | | /// </summary> |
| | | private bool ShouldShowDetailField(string fieldName, string fieldValue, PackageInfo packInfo) |
| | | { |
| | | if (string.IsNullOrEmpty(detailSearchText)) |
| | | return true; |
| | | |
| | | string searchLower = detailSearchText.ToLower(); |
| | | |
| | | // 搜索字段名 |
| | | if (fieldName.ToLower().Contains(searchLower)) |
| | | return true; |
| | | |
| | | // 搜索字段值 |
| | | if (fieldValue.ToLower().Contains(searchLower)) |
| | | return true; |
| | | |
| | | // 如果有详细信息,递归搜索 |
| | | if (packInfo.fieldDetails.ContainsKey(fieldName)) |
| | | { |
| | | var detail = packInfo.fieldDetails[fieldName]; |
| | | HashSet<object> visited = new HashSet<object>(); |
| | | if (RecursiveSearchInFieldDetail(detail, searchLower, visited)) |
| | | return true; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 递归搜索字段详情 |
| | | /// </summary> |
| | | private bool RecursiveSearchInFieldDetail(FieldDetail detail, string searchLower, HashSet<object> visited) |
| | | { |
| | | if (detail.value != null) |
| | | { |
| | | // 防止循环引用 |
| | | if (!detail.value.GetType().IsPrimitive && |
| | | detail.value.GetType() != typeof(string) && |
| | | !detail.value.GetType().IsEnum) |
| | | { |
| | | if (visited.Contains(detail.value)) |
| | | return false; |
| | | visited.Add(detail.value); |
| | | } |
| | | |
| | | // 搜索值本身 |
| | | if (detail.value.ToString().ToLower().Contains(searchLower)) |
| | | return true; |
| | | |
| | | // 如果是数组 |
| | | if (detail.isArray && detail.arrayValue != null) |
| | | { |
| | | foreach (var element in detail.arrayValue) |
| | | { |
| | | if (element != null && RecursiveSearchInObject(element, searchLower, visited)) |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | // 如果是列表 |
| | | if (detail.isList && detail.listValue != null) |
| | | { |
| | | foreach (var element in detail.listValue) |
| | | { |
| | | if (element != null && RecursiveSearchInObject(element, searchLower, visited)) |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | // 搜索子字段 |
| | | foreach (var subField in detail.subFieldDetails.Values) |
| | | { |
| | | if (RecursiveSearchInFieldDetail(subField, searchLower, visited)) |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 递归搜索对象内容 |
| | | /// </summary> |
| | | private bool RecursiveSearchInObject(object obj, string searchLower, HashSet<object> visited) |
| | | { |
| | | if (obj == null) return false; |
| | | |
| | | var type = obj.GetType(); |
| | | |
| | | // 特殊处理:如果是 GameNetPackBasic,优先检查 packUID |
| | | if (obj is GameNetPackBasic pack) |
| | | { |
| | | if (pack.packUID.ToString().Contains(searchLower)) |
| | | return true; |
| | | // 还要检查类型名 |
| | | if (pack.GetType().Name.ToLower().Contains(searchLower)) |
| | | return true; |
| | | } |
| | | |
| | | // 基本类型直接比较(包括所有数字类型) |
| | | if (type.IsPrimitive || type == typeof(string) || type.IsEnum) |
| | | { |
| | | string valueStr = obj.ToString(); |
| | | // 对于数字,直接比较字符串(不转小写,因为数字没有大小写) |
| | | if (type.IsPrimitive && !type.IsEnum) |
| | | { |
| | | return valueStr.Contains(searchLower); |
| | | } |
| | | // 对于字符串和枚举,转小写比较 |
| | | return valueStr.ToLower().Contains(searchLower); |
| | | } |
| | | |
| | | // 防止循环引用 |
| | | if (visited.Contains(obj)) |
| | | return false; |
| | | visited.Add(obj); |
| | | |
| | | // 限制递归深度,防止过深的嵌套 |
| | | if (visited.Count > 1000) |
| | | return false; |
| | | |
| | | // 特殊处理数组 |
| | | if (type.IsArray) |
| | | { |
| | | var array = obj as System.Array; |
| | | if (array != null) |
| | | { |
| | | foreach (var element in array) |
| | | { |
| | | if (element != null && RecursiveSearchInObject(element, searchLower, visited)) |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | // 特殊处理列表 |
| | | if (obj is System.Collections.IList list && !(obj is string)) |
| | | { |
| | | foreach (var element in list) |
| | | { |
| | | if (element != null && RecursiveSearchInObject(element, searchLower, visited)) |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | // 复杂对象,搜索所有字段 |
| | | var fields = type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); |
| | | foreach (var field in fields) |
| | | { |
| | | try |
| | | { |
| | | // 搜索字段名 |
| | | if (field.Name.ToLower().Contains(searchLower)) |
| | | return true; |
| | | |
| | | var value = field.GetValue(obj); |
| | | if (value != null && RecursiveSearchInObject(value, searchLower, visited)) |
| | | return true; |
| | | } |
| | | catch { } |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 辅助方法 |
| | | |
| | | /// <summary> |
| | | /// 根据对象ID获取对象名称 |
| | | /// </summary> |
| | | private string GetObjectName(uint objID) |
| | | { |
| | | try |
| | | { |
| | | var battleField = BattleManager.Instance?.GetBattleField(selectedGuid); |
| | | if (battleField != null && battleField.battleObjMgr != null) |
| | | { |
| | | var battleObj = battleField.battleObjMgr.GetBattleObject((int)objID); |
| | | if (battleObj != null && battleObj.teamHero != null) |
| | | { |
| | | return battleObj.teamHero.name; |
| | | } |
| | | } |
| | | } |
| | | catch { } |
| | | |
| | | return $"对象{objID}"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据技能ID获取技能名称 |
| | | /// </summary> |
| | | private string GetSkillName(uint skillID) |
| | | { |
| | | try |
| | | { |
| | | var skillConfig = SkillConfig.Get((int)skillID); |
| | | if (skillConfig != null && !string.IsNullOrEmpty(skillConfig.SkillName)) |
| | | { |
| | | return skillConfig.SkillName; |
| | | } |
| | | } |
| | | catch { } |
| | | |
| | | return $"技能{skillID}"; |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 高亮和滚动 |
| | | |
| | | /// <summary> |
| | | /// 开始高亮一个包 |
| | | /// </summary> |
| | | private void StartHighlight(PackageInfo package) |
| | | { |
| | | highlightedPackage = package; |
| | | highlightStartTime = EditorApplication.timeSinceStartup; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 判断包是否处于高亮状态 |
| | | /// </summary> |
| | | private bool IsPackageHighlighted(PackageInfo package) |
| | | { |
| | | if (highlightedPackage == null || highlightedPackage != package) |
| | | return false; |
| | | |
| | | double elapsed = EditorApplication.timeSinceStartup - highlightStartTime; |
| | | return elapsed < highlightDuration; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 滚动到选中的包位置 |
| | | /// </summary> |
| | | private void ScrollToSelectedPackage() |
| | | { |
| | | if (selectedPackageIndex < 0 || filteredPackages.Count == 0) |
| | | return; |
| | | |
| | | // 计算目标滚动位置 |
| | | // 每个包条目的大概高度(可根据实际情况调整) |
| | | float itemHeight = 60f; |
| | | float targetY = selectedPackageIndex * itemHeight; |
| | | |
| | | // 滚动到目标位置 |
| | | scrollPosition.y = targetY; |
| | | |
| | | Repaint(); |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 数据处理 |
| | | |
| | | private void RefreshGuidList() |