using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using BaseGames.Dialogue;
using BaseGames.Quest;
using BaseGames.EventChain;
namespace BaseGames.Editor.Modules
{
///
/// DataHub ID 生成模块 —— 扫描 QuestSO / NpcSO / DialogueSequenceSO / EventChainSO 资产,
/// 自动生成 Assets/_Game/Scripts/Core/GameIds.Generated.cs,
/// 提供编译期 ID 常量,消除代码中的魔法字符串。
///
public class IdCodegenModule : IDataModule, IDataModuleOrdered
{
private const string OutputPath = "Assets/_Game/Scripts/Core/GameIds.Generated.cs";
public string ModuleId => "idcodegen";
public string DisplayName => "ID 生成";
public string IconName => "d_cs Script Icon";
public int DisplayOrder => 140;
private Label _statusLabel;
private string _lastResult;
// ── IDataModule ───────────────────────────────────────────────────────
public void Initialize() { }
public void BuildListPane(VisualElement container, Action onSelected)
{
var desc = new Label(
"扫描项目中的 QuestSO / NpcSO / DialogueSequenceSO / EventChainSO 资产,\n" +
$"生成 {OutputPath} 常量文件。\n\n" +
"生成后在代码中通过 GameIdsGenerated.Quest.XXX 等访问。");
desc.style.whiteSpace = WhiteSpace.Normal;
desc.style.marginBottom = 12;
desc.style.paddingLeft = 8;
desc.style.paddingRight = 8;
desc.style.fontSize = 11;
container.Add(desc);
var btn = new Button(RunCodegen) { text = "⚡ 生成 GameIds.Generated.cs" };
btn.style.marginLeft = 8;
btn.style.marginRight = 8;
btn.style.height = 28;
container.Add(btn);
_statusLabel = new Label(_lastResult ?? "");
_statusLabel.style.whiteSpace = WhiteSpace.Normal;
_statusLabel.style.marginTop = 10;
_statusLabel.style.marginLeft = 8;
_statusLabel.style.marginRight = 8;
_statusLabel.style.fontSize = 11;
container.Add(_statusLabel);
}
public void BuildDetailPane(VisualElement container, UnityEngine.Object selected)
{
// 无需详情面板
}
public void OnActivated() { }
// ── 代码生成 ──────────────────────────────────────────────────────────
private void RunCodegen()
{
try
{
var quests = AssetOperations.FindAll()
.Where(q => !string.IsNullOrEmpty(q.questId))
.Select(q => q.questId)
.Distinct(StringComparer.Ordinal)
.OrderBy(s => s, StringComparer.Ordinal)
.ToList();
var npcs = AssetOperations.FindAll()
.Where(n => !string.IsNullOrEmpty(n.npcId))
.Select(n => n.npcId)
.Distinct(StringComparer.Ordinal)
.OrderBy(s => s, StringComparer.Ordinal)
.ToList();
var dialogues = AssetOperations.FindAll()
.Where(d => !string.IsNullOrEmpty(d.sequenceId))
.Select(d => d.sequenceId)
.Distinct(StringComparer.Ordinal)
.OrderBy(s => s, StringComparer.Ordinal)
.ToList();
var chains = AssetOperations.FindAll()
.Where(c => !string.IsNullOrEmpty(c.chainId))
.Select(c => c.chainId)
.Distinct(StringComparer.Ordinal)
.OrderBy(s => s, StringComparer.Ordinal)
.ToList();
string code = BuildSourceCode(quests, npcs, dialogues, chains);
string fullPath = Path.GetFullPath(OutputPath);
string dir = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
File.WriteAllText(fullPath, code, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
AssetDatabase.ImportAsset(OutputPath, ImportAssetOptions.ForceUpdate);
int total = quests.Count + npcs.Count + dialogues.Count + chains.Count;
_lastResult = $"✅ 生成完成({total} 个常量:Quest×{quests.Count} Npc×{npcs.Count} " +
$"Dialogue×{dialogues.Count} Chain×{chains.Count})";
}
catch (Exception e)
{
_lastResult = $"❌ 生成失败:{e.Message}";
Debug.LogException(e);
}
if (_statusLabel != null)
_statusLabel.text = _lastResult;
}
private static string BuildSourceCode(List quests, List npcs,
List dialogues, List chains)
{
var sb = new StringBuilder();
sb.AppendLine("// ");
sb.AppendLine("// 此文件由 DataHub > ID生成 模块自动生成,请勿手动编辑。");
sb.AppendLine("// 手动维护的 ID 常量请放在 GameIds.cs 中。");
sb.AppendLine("// ");
sb.AppendLine();
sb.AppendLine("namespace BaseGames.Core");
sb.AppendLine("{");
sb.AppendLine(" /// 自动生成的游戏资产 ID 常量。每次执行 DataHub > ID生成 后刷新。");
sb.AppendLine(" public static class GameIdsGenerated");
sb.AppendLine(" {");
AppendSection(sb, "Quest", "QuestSO.questId", quests, "Quest_");
AppendSection(sb, "Npc", "NpcSO.npcId", npcs, "NPC_");
AppendSection(sb, "Dialogue", "DialogueSequenceSO.sequenceId", dialogues, "DLG_");
AppendSection(sb, "Chain", "EventChainSO.chainId", chains, "Chain_");
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
private static void AppendSection(StringBuilder sb, string className, string docSource,
List ids, string stripPrefix)
{
sb.AppendLine($" /// 来自 {docSource} 的 ID 常量。");
sb.AppendLine($" public static class {className}");
sb.AppendLine(" {");
if (ids.Count == 0)
sb.AppendLine(" // 项目中暂无此类资产");
else
foreach (string id in ids)
sb.AppendLine($" public const string {ToFieldName(id, stripPrefix)} = \"{id}\";");
sb.AppendLine(" }");
sb.AppendLine();
}
///
/// 将原始 ID 字符串转为合法的 C# 标识符。
/// 剥离常见前缀(如 "Quest_"),然后将剩余部分中的非字母数字字符替换为下划线,
/// 确保不以数字开头。
///
private static string ToFieldName(string rawId, string stripPrefix)
{
string name = rawId;
if (!string.IsNullOrEmpty(stripPrefix) &&
name.StartsWith(stripPrefix, StringComparison.OrdinalIgnoreCase))
name = name.Substring(stripPrefix.Length);
// 将非字母数字字符替换为下划线
name = Regex.Replace(name, @"[^A-Za-z0-9_]", "_");
// 不能以数字开头
if (name.Length > 0 && char.IsDigit(name[0]))
name = "_" + name;
if (string.IsNullOrEmpty(name))
name = "_";
return name;
}
}
}