784 lines
29 KiB
C#
784 lines
29 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using OfficeOpenXml;
|
||
using ArtResource;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using Debug = UnityEngine.Debug;
|
||
|
||
namespace DesignTools.LimitedTimeEvent
|
||
{
|
||
/// <summary>
|
||
/// 限时事件投放配置工具
|
||
/// 读取 Docs/config/LimitedTimeEvent.xlsx 的 Series Sheet
|
||
/// Items字段通过读取 Item.xlsx 中 IType=102 的条目进行选择
|
||
/// </summary>
|
||
public class LimitedEventSeriesEditor : BaseDesignToolEditor
|
||
{
|
||
private const string LIMITED_TIME_EVENT_EXCEL_NAME = "LimitedTimeEvent.xlsx";
|
||
private const string ITEM_EXCEL_NAME = "Item.xlsx";
|
||
private const string SERIES_SHEET_NAME = "Jackpot";
|
||
private const string ITEM_SHEET_NAME = "Item";
|
||
private const string ART_SO_PATH = "Assets/Art_SubModule/Art_SO";
|
||
private const int HEADER_ROW = 1;
|
||
private const int DATA_START_ROW = 3;
|
||
|
||
// 列索引
|
||
private int colId = -1;
|
||
private int colBonusLv = -1;
|
||
private int colItems = -1;
|
||
private int colType = -1;
|
||
private int colProb = -1;
|
||
private int colNote = -1;
|
||
|
||
private readonly List<SeriesRowData> seriesDataList = new List<SeriesRowData>();
|
||
|
||
private readonly List<LimitedEventItemInfo> limitedEventItems = new List<LimitedEventItemInfo>();
|
||
private readonly Dictionary<int, LimitedEventItemInfo> limitedEventItemById = new Dictionary<int, LimitedEventItemInfo>();
|
||
private readonly Dictionary<string, ArtTableSO> artTableByName = new Dictionary<string, ArtTableSO>(StringComparer.OrdinalIgnoreCase);
|
||
|
||
[MenuItem("策划工具/限时事件/限时事件投放")]
|
||
public static void ShowWindow()
|
||
{
|
||
var window = GetWindow<LimitedEventSeriesEditor>("限时事件投放");
|
||
window.minSize = window.GetMinWindowSize();
|
||
window.Show();
|
||
}
|
||
|
||
protected override string GetDocsPathPrefKey()
|
||
{
|
||
return "LimitedEventSeriesEditor_DocsPath";
|
||
}
|
||
|
||
protected override string GetWindowTitle()
|
||
{
|
||
return "限时事件投放配置工具";
|
||
}
|
||
|
||
protected override Vector2 GetMinWindowSize()
|
||
{
|
||
return new Vector2(1100, 600);
|
||
}
|
||
|
||
protected override void LoadConfigData()
|
||
{
|
||
LoadAllArtTableSO();
|
||
LoadLimitedEventItems();
|
||
LoadSeriesSheet();
|
||
}
|
||
|
||
protected override void DrawDataEditor()
|
||
{
|
||
DrawTableHeader();
|
||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||
for (int i = 0; i < seriesDataList.Count; i++)
|
||
{
|
||
DrawSeriesRow(seriesDataList[i]);
|
||
}
|
||
EditorGUILayout.EndScrollView();
|
||
|
||
EditorGUILayout.Space(4);
|
||
DrawAddRemoveButtons();
|
||
}
|
||
|
||
protected override void SaveDataToExcel()
|
||
{
|
||
SaveSeriesSheet();
|
||
}
|
||
|
||
#region 数据加载
|
||
|
||
/// <summary>
|
||
/// 加载所有ArtTableSO资源
|
||
/// </summary>
|
||
private void LoadAllArtTableSO()
|
||
{
|
||
artTableByName.Clear();
|
||
|
||
string[] guids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { ART_SO_PATH });
|
||
foreach (string guid in guids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
ArtTableSO tableSO = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
|
||
if (tableSO == null || string.IsNullOrEmpty(tableSO.TableName))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
artTableByName[tableSO.TableName] = tableSO;
|
||
}
|
||
|
||
Debug.Log($"加载了 {artTableByName.Count} 个 ArtTableSO 资源表");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从Item.xlsx加载IType=102的道具
|
||
/// </summary>
|
||
private void LoadLimitedEventItems()
|
||
{
|
||
limitedEventItems.Clear();
|
||
limitedEventItemById.Clear();
|
||
|
||
string excelPath = GetDocsConfigFilePath(ITEM_EXCEL_NAME);
|
||
if (!File.Exists(excelPath))
|
||
{
|
||
Debug.LogWarning($"未找到 {ITEM_EXCEL_NAME}: {excelPath}");
|
||
return;
|
||
}
|
||
|
||
using (var package = new ExcelPackage(new FileInfo(excelPath)))
|
||
{
|
||
var worksheet = package.Workbook.Worksheets[ITEM_SHEET_NAME];
|
||
if (worksheet == null || worksheet.Dimension == null)
|
||
{
|
||
Debug.LogWarning($"{ITEM_EXCEL_NAME} 中未找到 Sheet: {ITEM_SHEET_NAME}");
|
||
return;
|
||
}
|
||
|
||
int idCol = -1, nameCol = -1, iTypeCol = -1, resCol = -1;
|
||
int columnCount = worksheet.Dimension.End.Column;
|
||
|
||
for (int col = 1; col <= columnCount; col++)
|
||
{
|
||
string header = worksheet.Cells[HEADER_ROW, col].Text.Trim();
|
||
if (header == "Id") idCol = col;
|
||
else if (header == "Name") nameCol = col;
|
||
else if (header == "IType") iTypeCol = col;
|
||
else if (header == "Res") resCol = col;
|
||
}
|
||
|
||
if (idCol < 0 || nameCol < 0 || iTypeCol < 0)
|
||
{
|
||
throw new Exception("Item.xlsx表结构不正确,缺少必要列(Id/Name/IType)");
|
||
}
|
||
|
||
int rowCount = worksheet.Dimension.End.Row;
|
||
for (int row = DATA_START_ROW; row <= rowCount; row++)
|
||
{
|
||
string idText = worksheet.Cells[row, idCol].Text.Trim();
|
||
if (string.IsNullOrEmpty(idText)) continue;
|
||
if (!int.TryParse(idText, out int id)) continue;
|
||
|
||
string iTypeText = worksheet.Cells[row, iTypeCol].Text.Trim();
|
||
if (!int.TryParse(iTypeText, out int iType)) continue;
|
||
|
||
if (iType == 102)
|
||
{
|
||
string name = worksheet.Cells[row, nameCol].Text.Trim();
|
||
string res = resCol > 0 ? worksheet.Cells[row, resCol].Text.Trim() : "";
|
||
LimitedEventItemInfo itemInfo = new LimitedEventItemInfo
|
||
{
|
||
Id = id,
|
||
Name = name,
|
||
Res = res,
|
||
PreviewSprite = GetPreviewSpriteByRes(res)
|
||
};
|
||
|
||
limitedEventItems.Add(itemInfo);
|
||
limitedEventItemById[id] = itemInfo;
|
||
}
|
||
}
|
||
}
|
||
|
||
limitedEventItems.Sort((left, right) => left.Id.CompareTo(right.Id));
|
||
|
||
Debug.Log($"从Item.xlsx加载了 {limitedEventItems.Count} 个限时事件道具(IType=102)");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载Series Sheet数据
|
||
/// </summary>
|
||
private void LoadSeriesSheet()
|
||
{
|
||
seriesDataList.Clear();
|
||
|
||
string excelPath = GetDocsConfigFilePath(LIMITED_TIME_EVENT_EXCEL_NAME);
|
||
if (!File.Exists(excelPath))
|
||
{
|
||
throw new FileNotFoundException($"未找到 {LIMITED_TIME_EVENT_EXCEL_NAME}", excelPath);
|
||
}
|
||
|
||
using (var package = new ExcelPackage(new FileInfo(excelPath)))
|
||
{
|
||
var worksheet = package.Workbook.Worksheets[SERIES_SHEET_NAME];
|
||
if (worksheet == null)
|
||
{
|
||
throw new Exception($"{LIMITED_TIME_EVENT_EXCEL_NAME} 中未找到 Sheet: {SERIES_SHEET_NAME}");
|
||
}
|
||
|
||
if (worksheet.Dimension == null)
|
||
{
|
||
throw new Exception($"{LIMITED_TIME_EVENT_EXCEL_NAME}/{SERIES_SHEET_NAME} 没有可读取的数据");
|
||
}
|
||
|
||
colId = FindColumnIndex(worksheet, "Id");
|
||
colBonusLv = FindColumnIndex(worksheet, "BonusLv");
|
||
colItems = FindColumnIndex(worksheet, "Items");
|
||
colType = FindColumnIndex(worksheet, "Type");
|
||
colProb = FindColumnIndex(worksheet, "Prob");
|
||
colNote = FindColumnIndex(worksheet, "备注");
|
||
|
||
if (colId < 0 || colBonusLv < 0 || colItems < 0 || colType < 0 || colProb < 0)
|
||
{
|
||
throw new Exception($"{SERIES_SHEET_NAME} 表结构不正确,缺少必要列(Id/BonusLv/Items/Type/Prob)");
|
||
}
|
||
|
||
int rowCount = worksheet.Dimension.End.Row;
|
||
for (int row = DATA_START_ROW; row <= rowCount; row++)
|
||
{
|
||
string idText = worksheet.Cells[row, colId].Text.Trim();
|
||
if (string.IsNullOrEmpty(idText)) continue;
|
||
if (!int.TryParse(idText, out int id)) continue;
|
||
|
||
var data = new SeriesRowData
|
||
{
|
||
RowIndex = row,
|
||
Id = id,
|
||
BonusLv = ParseInt(worksheet.Cells[row, colBonusLv].Text),
|
||
ItemsJson = worksheet.Cells[row, colItems].Text.Trim(),
|
||
Type = ParseInt(worksheet.Cells[row, colType].Text),
|
||
Prob = ParseInt(worksheet.Cells[row, colProb].Text),
|
||
Note = colNote > 0 ? worksheet.Cells[row, colNote].Text.Trim() : ""
|
||
};
|
||
|
||
// 解析Items JSON中的第一个Id
|
||
data.ParsedItemId = ParseItemIdFromJson(data.ItemsJson);
|
||
|
||
seriesDataList.Add(data);
|
||
}
|
||
}
|
||
|
||
Debug.Log($"限时事件投放配置读取完成,共加载 {seriesDataList.Count} 条数据");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region UI绘制
|
||
|
||
private void DrawTableHeader()
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("Id", EditorStyles.boldLabel, GUILayout.Width(50));
|
||
EditorGUILayout.LabelField("BonusLv", EditorStyles.boldLabel, GUILayout.Width(70));
|
||
EditorGUILayout.LabelField("Items (限时事件道具)", EditorStyles.boldLabel, GUILayout.Width(350));
|
||
EditorGUILayout.LabelField("Type", EditorStyles.boldLabel, GUILayout.Width(60));
|
||
EditorGUILayout.LabelField("Prob", EditorStyles.boldLabel, GUILayout.Width(60));
|
||
EditorGUILayout.LabelField("备注", EditorStyles.boldLabel, GUILayout.MinWidth(120));
|
||
EditorGUILayout.LabelField("", GUILayout.Width(20)); // 删除按钮占位
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawSeriesRow(SeriesRowData data)
|
||
{
|
||
EditorGUILayout.BeginHorizontal("box");
|
||
|
||
// Id
|
||
data.Id = EditorGUILayout.IntField(data.Id, GUILayout.Width(50));
|
||
|
||
// BonusLv
|
||
data.BonusLv = EditorGUILayout.IntField(data.BonusLv, GUILayout.Width(70));
|
||
|
||
// Items - 使用下拉选择IType=102的道具
|
||
DrawItemsField(data);
|
||
|
||
// Type
|
||
data.Type = EditorGUILayout.IntField(data.Type, GUILayout.Width(60));
|
||
|
||
// Prob
|
||
data.Prob = EditorGUILayout.IntField(data.Prob, GUILayout.Width(60));
|
||
|
||
// 备注
|
||
data.Note = EditorGUILayout.TextField(data.Note, GUILayout.MinWidth(120));
|
||
|
||
// 删除按钮
|
||
if (GUILayout.Button("×", GUILayout.Width(20)))
|
||
{
|
||
if (EditorUtility.DisplayDialog("确认删除", $"确定删除 Id={data.Id} 的行吗?", "确定", "取消"))
|
||
{
|
||
seriesDataList.Remove(data);
|
||
GUIUtility.ExitGUI();
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void DrawItemsField(SeriesRowData data)
|
||
{
|
||
LimitedEventItemInfo itemInfo = GetLimitedEventItemInfo(data.ParsedItemId);
|
||
bool hasPreview = itemInfo?.PreviewSprite != null && itemInfo.PreviewSprite.texture != null;
|
||
|
||
EditorGUILayout.BeginHorizontal(
|
||
"box",
|
||
GUILayout.Width(350),
|
||
GUILayout.MinHeight(hasPreview ? 68f : 42f));
|
||
|
||
if (hasPreview)
|
||
{
|
||
DrawSpritePreview(itemInfo.PreviewSprite, 48f, string.Empty);
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical();
|
||
|
||
if (limitedEventItems.Count > 0)
|
||
{
|
||
string buttonText = itemInfo != null
|
||
? $"{itemInfo.Id} - {itemInfo.Name}"
|
||
: data.ParsedItemId > 0
|
||
? $"未找到道具 {data.ParsedItemId}"
|
||
: "选择限时事件道具";
|
||
|
||
if (GUILayout.Button(buttonText, GUILayout.Height(22)))
|
||
{
|
||
SeriesRowData capturedData = data;
|
||
LimitedEventItemPickerWindow.Show(limitedEventItems, position, data.ParsedItemId, selectedItem =>
|
||
{
|
||
capturedData.ParsedItemId = selectedItem.Id;
|
||
capturedData.ItemsJson = BuildItemsJson(selectedItem.Id);
|
||
Repaint();
|
||
});
|
||
}
|
||
|
||
if (itemInfo != null && !string.IsNullOrEmpty(itemInfo.Res))
|
||
{
|
||
EditorGUILayout.LabelField(itemInfo.Res, EditorStyles.miniLabel);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.LabelField("未加载到IType=102的道具", EditorStyles.miniLabel);
|
||
data.ItemsJson = EditorGUILayout.TextField(data.ItemsJson);
|
||
}
|
||
|
||
EditorGUILayout.LabelField(data.ItemsJson, EditorStyles.miniLabel);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void DrawAddRemoveButtons()
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.FlexibleSpace();
|
||
|
||
if (GUILayout.Button("+ 添加行", GUILayout.Width(100), GUILayout.Height(25)))
|
||
{
|
||
int newId = seriesDataList.Count > 0 ? seriesDataList.Max(x => x.Id) + 1 : 1;
|
||
int defaultItemId = limitedEventItems.Count > 0 ? limitedEventItems[0].Id : 0;
|
||
|
||
seriesDataList.Add(new SeriesRowData
|
||
{
|
||
RowIndex = -1,
|
||
Id = newId,
|
||
BonusLv = 1,
|
||
ItemsJson = defaultItemId > 0 ? $"[{{\"Id\":{defaultItemId},\"Num\":1}}]" : "[]",
|
||
Type = 0,
|
||
Prob = 0,
|
||
Note = "",
|
||
ParsedItemId = defaultItemId
|
||
});
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 数据保存
|
||
|
||
private void SaveSeriesSheet()
|
||
{
|
||
if (seriesDataList.Count == 0)
|
||
{
|
||
EditorUtility.DisplayDialog("提示", "当前没有可保存的数据,请先加载配置。", "确定");
|
||
return;
|
||
}
|
||
|
||
string excelPath = GetDocsConfigFilePath(LIMITED_TIME_EVENT_EXCEL_NAME);
|
||
if (!File.Exists(excelPath))
|
||
{
|
||
EditorUtility.DisplayDialog("错误", $"未找到文件:{excelPath}", "确定");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
using (var package = new ExcelPackage(new FileInfo(excelPath)))
|
||
{
|
||
var worksheet = package.Workbook.Worksheets[SERIES_SHEET_NAME];
|
||
if (worksheet == null)
|
||
{
|
||
EditorUtility.DisplayDialog("错误", $"未找到 Sheet: {SERIES_SHEET_NAME}", "确定");
|
||
return;
|
||
}
|
||
|
||
// 清除旧数据行(从DATA_START_ROW开始)
|
||
int oldRowCount = worksheet.Dimension?.End.Row ?? 0;
|
||
for (int row = DATA_START_ROW; row <= oldRowCount; row++)
|
||
{
|
||
for (int col = 1; col <= worksheet.Dimension.End.Column; col++)
|
||
{
|
||
worksheet.Cells[row, col].Value = null;
|
||
}
|
||
}
|
||
|
||
// 写入新数据
|
||
for (int i = 0; i < seriesDataList.Count; i++)
|
||
{
|
||
int row = DATA_START_ROW + i;
|
||
var data = seriesDataList[i];
|
||
|
||
worksheet.Cells[row, colId].Value = data.Id;
|
||
worksheet.Cells[row, colBonusLv].Value = data.BonusLv;
|
||
worksheet.Cells[row, colItems].Value = data.ItemsJson;
|
||
worksheet.Cells[row, colType].Value = data.Type;
|
||
worksheet.Cells[row, colProb].Value = data.Prob;
|
||
|
||
if (colNote > 0)
|
||
{
|
||
worksheet.Cells[row, colNote].Value = data.Note;
|
||
}
|
||
}
|
||
|
||
package.Save();
|
||
}
|
||
|
||
EditorUtility.DisplayDialog("成功", "限时事件投放配置已保存到 Excel!", "确定");
|
||
Debug.Log("限时事件投放配置保存成功");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
EditorUtility.DisplayDialog("错误", $"保存失败: {e.Message}", "确定");
|
||
Debug.LogError($"保存限时事件投放配置失败: {e}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 工具方法
|
||
|
||
private LimitedEventItemInfo GetLimitedEventItemInfo(int itemId)
|
||
{
|
||
if (itemId <= 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
limitedEventItemById.TryGetValue(itemId, out LimitedEventItemInfo itemInfo);
|
||
return itemInfo;
|
||
}
|
||
|
||
private string BuildItemsJson(int itemId)
|
||
{
|
||
return itemId > 0 ? $"[{{\"Id\":{itemId},\"Num\":1}}]" : "[]";
|
||
}
|
||
|
||
private Sprite GetPreviewSpriteByRes(string res)
|
||
{
|
||
if (!TryParseRes(res, out string tableName, out string artItemName))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (!artTableByName.TryGetValue(tableName, out ArtTableSO table) || table == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
ArtItemData item = table.Items.FirstOrDefault(x => x.Name == artItemName);
|
||
return item?.Sprite;
|
||
}
|
||
|
||
private static bool TryParseRes(string res, out string tableName, out string artItemName)
|
||
{
|
||
tableName = string.Empty;
|
||
artItemName = string.Empty;
|
||
|
||
if (string.IsNullOrWhiteSpace(res))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
string[] parts = res.Split(new[] { ',' }, 2, StringSplitOptions.None);
|
||
if (parts.Length < 2)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
tableName = parts[0].Trim();
|
||
artItemName = parts[1].Trim();
|
||
return !string.IsNullOrEmpty(tableName) && !string.IsNullOrEmpty(artItemName);
|
||
}
|
||
|
||
private static void DrawSpritePreview(Sprite sprite, float size, string emptyText)
|
||
{
|
||
Rect previewRect = GUILayoutUtility.GetRect(size, size, GUILayout.Width(size), GUILayout.Height(size));
|
||
DrawSpritePreview(previewRect, sprite, emptyText);
|
||
}
|
||
|
||
private static void DrawSpritePreview(Rect previewRect, Sprite sprite, string emptyText)
|
||
{
|
||
if (sprite != null && sprite.texture != null)
|
||
{
|
||
Rect texCoords = sprite.textureRect;
|
||
Texture2D tex = sprite.texture;
|
||
Rect normalizedCoords = new Rect(
|
||
texCoords.x / tex.width,
|
||
texCoords.y / tex.height,
|
||
texCoords.width / tex.width,
|
||
texCoords.height / tex.height
|
||
);
|
||
|
||
EditorGUI.DrawRect(previewRect, new Color(0.4f, 0.4f, 0.4f, 1f));
|
||
|
||
float aspect = texCoords.height <= 0f ? 1f : texCoords.width / texCoords.height;
|
||
Rect drawRect = previewRect;
|
||
if (aspect > 1f)
|
||
{
|
||
float newHeight = previewRect.height / aspect;
|
||
drawRect.y += (previewRect.height - newHeight) * 0.5f;
|
||
drawRect.height = newHeight;
|
||
}
|
||
else
|
||
{
|
||
float newWidth = previewRect.width * aspect;
|
||
drawRect.x += (previewRect.width - newWidth) * 0.5f;
|
||
drawRect.width = newWidth;
|
||
}
|
||
|
||
GUI.DrawTextureWithTexCoords(drawRect, tex, normalizedCoords);
|
||
return;
|
||
}
|
||
|
||
EditorGUI.DrawRect(previewRect, new Color(0.3f, 0.3f, 0.3f, 1f));
|
||
GUIStyle emptyStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel)
|
||
{
|
||
wordWrap = true,
|
||
alignment = TextAnchor.MiddleCenter
|
||
};
|
||
GUI.Label(previewRect, emptyText, emptyStyle);
|
||
}
|
||
|
||
private int FindColumnIndex(ExcelWorksheet worksheet, string columnName)
|
||
{
|
||
int columnCount = worksheet.Dimension?.End.Column ?? 0;
|
||
for (int col = 1; col <= columnCount; col++)
|
||
{
|
||
if (string.Equals(worksheet.Cells[HEADER_ROW, col].Text.Trim(), columnName, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return col;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
private static int ParseInt(string text)
|
||
{
|
||
if (int.TryParse(text?.Trim(), out int value))
|
||
return value;
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从Items的JSON中解析出第一个Id
|
||
/// 输入格式: [{"Id":100011,"Num":1}]
|
||
/// </summary>
|
||
private static int ParseItemIdFromJson(string json)
|
||
{
|
||
if (string.IsNullOrEmpty(json)) return 0;
|
||
|
||
int idIndex = json.IndexOf("\"Id\"", StringComparison.OrdinalIgnoreCase);
|
||
if (idIndex < 0) return 0;
|
||
|
||
int colonIndex = json.IndexOf(':', idIndex);
|
||
if (colonIndex < 0) return 0;
|
||
|
||
int start = colonIndex + 1;
|
||
while (start < json.Length && char.IsWhiteSpace(json[start]))
|
||
{
|
||
start++;
|
||
}
|
||
|
||
int end = start;
|
||
while (end < json.Length && (char.IsDigit(json[end]) || json[end] == '-'))
|
||
{
|
||
end++;
|
||
}
|
||
|
||
if (end > start && int.TryParse(json.Substring(start, end - start), out int id))
|
||
return id;
|
||
|
||
return 0;
|
||
}
|
||
|
||
#endregion
|
||
|
||
[Serializable]
|
||
private class LimitedEventItemInfo
|
||
{
|
||
public int Id;
|
||
public string Name;
|
||
public string Res;
|
||
public Sprite PreviewSprite;
|
||
}
|
||
|
||
private class LimitedEventItemPickerWindow : EditorWindow
|
||
{
|
||
private readonly List<LimitedEventItemInfo> allItems = new List<LimitedEventItemInfo>();
|
||
private Action<LimitedEventItemInfo> onSelect;
|
||
private string searchText = string.Empty;
|
||
private Vector2 scrollPosition;
|
||
private int currentItemId;
|
||
|
||
private const float IMAGE_SIZE = 84f;
|
||
private const float ROW_HEIGHT_WITH_PREVIEW = 108f;
|
||
private const float ROW_HEIGHT_NO_PREVIEW = 54f;
|
||
|
||
public static void Show(List<LimitedEventItemInfo> items, Rect ownerWindowRect, int currentItemId, Action<LimitedEventItemInfo> onSelect)
|
||
{
|
||
if (items == null || items.Count == 0)
|
||
{
|
||
EditorUtility.DisplayDialog("提示", "未加载到 IType=102 的道具", "确定");
|
||
return;
|
||
}
|
||
|
||
LimitedEventItemPickerWindow window = CreateInstance<LimitedEventItemPickerWindow>();
|
||
window.titleContent = new GUIContent("选择限时事件道具");
|
||
window.minSize = new Vector2(620, 520);
|
||
window.allItems.Clear();
|
||
window.allItems.AddRange(items.OrderBy(x => x.Id));
|
||
window.currentItemId = currentItemId;
|
||
window.onSelect = onSelect;
|
||
window.position = GetCenteredRect(ownerWindowRect, window.minSize);
|
||
window.ShowUtility();
|
||
}
|
||
|
||
private static Rect GetCenteredRect(Rect ownerWindowRect, Vector2 windowSize)
|
||
{
|
||
float x = ownerWindowRect.x + (ownerWindowRect.width - windowSize.x) * 0.5f;
|
||
float y = ownerWindowRect.y + (ownerWindowRect.height - windowSize.y) * 0.5f;
|
||
return new Rect(x, y, windowSize.x, windowSize.y);
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
DrawToolbar();
|
||
EditorGUILayout.Space(4f);
|
||
DrawItemList();
|
||
}
|
||
|
||
private void DrawToolbar()
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||
EditorGUILayout.LabelField("搜索", GUILayout.Width(40));
|
||
searchText = EditorGUILayout.TextField(searchText, EditorStyles.toolbarSearchField);
|
||
if (GUILayout.Button("×", EditorStyles.miniButton, GUILayout.Width(20)))
|
||
{
|
||
searchText = string.Empty;
|
||
GUI.FocusControl(null);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
int filteredCount = allItems.Count(MatchSearch);
|
||
EditorGUILayout.LabelField($"共 {filteredCount} 个限时事件道具,点击即可选择", EditorStyles.miniLabel);
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawItemList()
|
||
{
|
||
List<LimitedEventItemInfo> filteredItems = allItems.Where(MatchSearch).ToList();
|
||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||
|
||
foreach (LimitedEventItemInfo item in filteredItems)
|
||
{
|
||
DrawItemRow(item);
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
}
|
||
|
||
private void DrawItemRow(LimitedEventItemInfo item)
|
||
{
|
||
bool isCurrent = item.Id == currentItemId;
|
||
bool hasPreview = item.PreviewSprite != null && item.PreviewSprite.texture != null;
|
||
float rowHeight = hasPreview ? ROW_HEIGHT_WITH_PREVIEW : ROW_HEIGHT_NO_PREVIEW;
|
||
GUI.backgroundColor = isCurrent ? new Color(0.8f, 1f, 0.8f) : Color.white;
|
||
|
||
EditorGUILayout.BeginHorizontal("box", GUILayout.ExpandWidth(true), GUILayout.MinHeight(rowHeight));
|
||
|
||
if (hasPreview)
|
||
{
|
||
Rect imageRect = GUILayoutUtility.GetRect(IMAGE_SIZE, IMAGE_SIZE, GUILayout.Width(IMAGE_SIZE), GUILayout.Height(IMAGE_SIZE));
|
||
DrawSpritePreview(imageRect, item.PreviewSprite, string.Empty);
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical();
|
||
|
||
GUIStyle nameStyle = new GUIStyle(EditorStyles.boldLabel)
|
||
{
|
||
wordWrap = true,
|
||
alignment = TextAnchor.UpperLeft
|
||
};
|
||
GUILayout.Label($"{item.Id} - {item.Name}", nameStyle);
|
||
|
||
if (!string.IsNullOrEmpty(item.Res))
|
||
{
|
||
GUIStyle resStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel)
|
||
{
|
||
wordWrap = true,
|
||
alignment = TextAnchor.UpperLeft
|
||
};
|
||
GUILayout.Label(item.Res, resStyle);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
if (GUILayout.Button("选择", EditorStyles.miniButton))
|
||
{
|
||
onSelect?.Invoke(item);
|
||
Close();
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
GUI.backgroundColor = Color.white;
|
||
}
|
||
|
||
private bool MatchSearch(LimitedEventItemInfo item)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(searchText))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return item.Id.ToString().Contains(searchText, StringComparison.OrdinalIgnoreCase)
|
||
|| (!string.IsNullOrEmpty(item.Name) && item.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||
|| (!string.IsNullOrEmpty(item.Res) && item.Res.Contains(searchText, StringComparison.OrdinalIgnoreCase));
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
private class SeriesRowData
|
||
{
|
||
public int RowIndex;
|
||
public int Id;
|
||
public int BonusLv;
|
||
public string ItemsJson;
|
||
public int Type;
|
||
public int Prob;
|
||
public string Note;
|
||
|
||
/// <summary>
|
||
/// 从ItemsJson解析出的道具Id,用于下拉选择
|
||
/// </summary>
|
||
public int ParsedItemId;
|
||
}
|
||
}
|
||
}
|