更新
This commit is contained in:
@@ -84,10 +84,34 @@ class ProjectScanner:
|
||||
elif entry == "work":
|
||||
self.work_dir = p
|
||||
|
||||
# 2. 项目名
|
||||
if self.config_dir:
|
||||
base = os.path.basename(self.config_dir)
|
||||
self.project_name = base.replace("_config", "")
|
||||
# 1b. 配置目录兜底:部分项目(如 HM6)把配置表放在 P-XXX_game/config
|
||||
# 而非独立的 *_config 目录。优先顶层 config 目录,其次游戏目录下的 config 子目录,
|
||||
# 取其中确实含 xlsx/csv 的那个作为 config_dir。
|
||||
if not self.config_dir:
|
||||
candidates = []
|
||||
top_config = os.path.join(self.root, "config")
|
||||
if os.path.isdir(top_config):
|
||||
candidates.append(top_config)
|
||||
if self.game_dir:
|
||||
gc = os.path.join(self.game_dir, "config")
|
||||
if os.path.isdir(gc):
|
||||
candidates.append(gc)
|
||||
for c in candidates:
|
||||
try:
|
||||
has_table = any(f.endswith((".xlsx", ".csv")) for f in os.listdir(c))
|
||||
except OSError:
|
||||
has_table = False
|
||||
if has_table:
|
||||
self.config_dir = c
|
||||
break
|
||||
|
||||
# 2. 项目名:优先从 *_config 目录名提取;否则从 *_game 目录名提取;再否则用 config 目录名
|
||||
if self.config_dir and os.path.basename(self.config_dir).endswith("_config"):
|
||||
self.project_name = os.path.basename(self.config_dir).replace("_config", "")
|
||||
elif self.game_dir and os.path.basename(self.game_dir).endswith("_game"):
|
||||
self.project_name = os.path.basename(self.game_dir).replace("_game", "")
|
||||
elif self.config_dir:
|
||||
self.project_name = os.path.basename(self.config_dir)
|
||||
|
||||
# 3. 数据表目录(csv_dir): work/cvs 优先(若存在),否则用 config_dir 本身(含xlsx)
|
||||
# find_file 优先查找 .xlsx,其次 .csv,两种格式都直接读取
|
||||
@@ -98,49 +122,79 @@ class ProjectScanner:
|
||||
if not self.csv_dir:
|
||||
self.csv_dir = self.config_dir
|
||||
|
||||
# 4. 搜索根目录:优先 Resources_moved,其次 Resources
|
||||
# 4&5. 图标目录: 在 game_dir 下递归搜索 atlas_ui 目录,
|
||||
# 在其中找 atlas_* 子目录(只保留含图片的),resources_dir 记为 atlas_ui 的父目录。
|
||||
# 直接按 atlas_ui 定位可兼容 P-L12(unity/Assets/...) 与 L17(Assets/...) 任意层级,
|
||||
# 避免误匹配同名但错误的 Resources_moved 目录。
|
||||
if self.game_dir:
|
||||
for root, dirs, _ in os.walk(self.game_dir):
|
||||
for name in ["Resources_moved", "Resources"]:
|
||||
if name in dirs:
|
||||
self.resources_dir = os.path.join(root, name)
|
||||
break
|
||||
if self.resources_dir:
|
||||
break
|
||||
|
||||
# 5. 图标目录 (递归搜索 atlas_* 子目录,只保留包含图片的叶子目录)
|
||||
if self.resources_dir:
|
||||
for root, dirs, files in os.walk(self.resources_dir):
|
||||
if os.path.basename(root) == "atlas_ui":
|
||||
self.resources_dir = os.path.dirname(root)
|
||||
for d in dirs:
|
||||
if d.startswith("atlas_"):
|
||||
dp = os.path.join(root, d)
|
||||
# 检查是否包含图片文件
|
||||
has_img = any(f.lower().endswith((".png", ".jpg", ".jpeg")) for f in os.listdir(dp))
|
||||
if has_img:
|
||||
if any(f.lower().endswith((".png", ".jpg", ".jpeg"))
|
||||
for f in os.listdir(dp)):
|
||||
self.icon_dirs[d] = dp
|
||||
break # 找到第一个 atlas_ui 即止
|
||||
|
||||
# 6. Scripts 目录
|
||||
# 6. Scripts 目录: 递归收集所有名为 Scripts 的目录,挑选"真实游戏脚本"目录。
|
||||
# 规则:优先"直接位于 Assets 目录下"的 Scripts(Unity 游戏源码根目录约定,
|
||||
# 如 unity/Assets/Scripts 或 Assets/Scripts),排除 TutorialInfo/示例等非源码目录;
|
||||
# 在其中再优先含游戏常量文件(GameError/GameText/Error/Text/Event/Msg Const)者;
|
||||
# 无 Assets/Scripts 时回退到全部候选中"含游戏常量"或".cs 最多"的目录。
|
||||
# 同时跳过 Library(Unity 包缓存, 文件巨多且非游戏源码),避免误选并加速。
|
||||
if self.game_dir:
|
||||
unity = os.path.join(self.game_dir, "unity")
|
||||
assets = os.path.join(unity, "Assets") if os.path.isdir(unity) else None
|
||||
if assets:
|
||||
scripts = os.path.join(assets, "Scripts")
|
||||
if os.path.isdir(scripts):
|
||||
self.scripts_dir = scripts
|
||||
all_scripts = []
|
||||
for root, dirs, _ in os.walk(self.game_dir):
|
||||
if "Library" in dirs:
|
||||
dirs.remove("Library") # 不进入 Unity 包缓存
|
||||
if os.path.basename(root) == "Scripts":
|
||||
all_scripts.append(root)
|
||||
|
||||
# 7. GameTextConst.cs
|
||||
# 直接位于 Assets 下的 Scripts 目录(游戏源码根)
|
||||
assets_scripts = [p for p in all_scripts
|
||||
if os.path.basename(os.path.dirname(p)) == "Assets"]
|
||||
candidates = assets_scripts if assets_scripts else all_scripts
|
||||
|
||||
def _has_game_const(p):
|
||||
# 递归查找游戏常量文件(常量常嵌套在子目录, 不能只看直接子文件)
|
||||
try:
|
||||
for r, _, files in os.walk(p):
|
||||
for f in files:
|
||||
if f.endswith(".cs") and "Const" in f and any(
|
||||
k in f for k in ("GameError", "GameText", "Error", "Text", "Event", "Msg")
|
||||
):
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _score(p):
|
||||
return 1000 if _has_game_const(p) else 0
|
||||
|
||||
if candidates:
|
||||
self.scripts_dir = max(candidates, key=_score)
|
||||
|
||||
# 7. 系统提示常量文件: 收集所有 *Const*.cs,
|
||||
# 优先 ErrorConst/GameErrorConst/GameTextConst/TextConst,否则取第一个。
|
||||
if self.scripts_dir:
|
||||
const_files = []
|
||||
for root, _, files in os.walk(self.scripts_dir):
|
||||
for f in files:
|
||||
if f.lower().endswith(".cs") and "const" in f.lower() and ("text" in f.lower() or "game" in f.lower()):
|
||||
self.game_text_const = os.path.join(root, f)
|
||||
return
|
||||
# 宽松匹配:任意 *Const*.cs
|
||||
for root, _, files in os.walk(self.scripts_dir):
|
||||
for f in files:
|
||||
if "Const" in f and f.endswith(".cs"):
|
||||
self.game_text_const = os.path.join(root, f)
|
||||
return
|
||||
if f.endswith(".cs") and "Const" in f:
|
||||
const_files.append(os.path.join(root, f))
|
||||
if const_files:
|
||||
def _pick(sub):
|
||||
for p in const_files:
|
||||
if sub in os.path.basename(p):
|
||||
return p
|
||||
return None
|
||||
self.game_text_const = (
|
||||
_pick("GameErrorConst") or _pick("ErrorConst")
|
||||
or _pick("GameTextConst") or _pick("TextConst")
|
||||
or const_files[0]
|
||||
)
|
||||
|
||||
def find_file(self, name):
|
||||
"""在 csv_dir 或 config_dir 中查找文件(优先 .xlsx,其次 .csv)"""
|
||||
@@ -253,7 +307,7 @@ def find_image(icon_name, icon_dirs, icon_key):
|
||||
|
||||
|
||||
# ================================================================
|
||||
# 四、GameTextConst.cs 自动解析
|
||||
# 四、系统提示常量 / 事件文本解析
|
||||
# ================================================================
|
||||
|
||||
def parse_game_text_const(cs_path, scripts_dir):
|
||||
@@ -308,15 +362,85 @@ def parse_game_text_const(cs_path, scripts_dir):
|
||||
continue
|
||||
|
||||
# 去重文本值
|
||||
# 优先保留"被其他源码引用"的常量;若引用扫描未命中(项目引用方式不同,如 L17 把错误码集中管理),
|
||||
# 则回退为使用常量文件中的全部 public const string,避免整章变空。
|
||||
seen = set()
|
||||
result = []
|
||||
for name, text in all_consts.items():
|
||||
if name in referenced and text not in seen:
|
||||
src_items = ((n, t) for n, t in all_consts.items() if n in referenced) if referenced else all_consts.items()
|
||||
for name, text in src_items:
|
||||
if text not in seen:
|
||||
seen.add(text)
|
||||
result.append(text)
|
||||
return result
|
||||
|
||||
|
||||
def parse_event_system_tips(scripts_dir):
|
||||
"""
|
||||
扫描 scripts_dir 下所有 .cs 文件,提取通过 event_system_tip 事件触发显示的
|
||||
系统提示文本 —— 即形如
|
||||
TriggerEventGlobal(GameEventConst.event_system_tip, "文本")
|
||||
的第二个字符串参数(真正的弹窗提示文案)。
|
||||
跳过 // 注释行。返回去重后的文本列表。
|
||||
|
||||
说明:不少项目(如 L17)并未把系统提示集中写在 GameTextConst/GameErrorConst 里,
|
||||
而是散落在成百上千个 .cs 文件中的 event_system_tip 事件调用里,
|
||||
GameEventConst 本身只继承 FGameEventConst 定义事件名常量,
|
||||
因此必须扫描调用现场才能拿到真实文案。
|
||||
"""
|
||||
if not scripts_dir or not os.path.isdir(scripts_dir):
|
||||
return []
|
||||
|
||||
# event_system_tip 之后(允许跨空白/换行)紧跟逗号与字符串字面量
|
||||
pattern = re.compile(r'event_system_tip\b[^;{]*?,\s*"((?:[^"\\]|\\.)*)"', re.DOTALL)
|
||||
|
||||
seen = set()
|
||||
result = []
|
||||
for root, _, files in os.walk(scripts_dir):
|
||||
for fname in files:
|
||||
if not fname.endswith(".cs"):
|
||||
continue
|
||||
try:
|
||||
with open(os.path.join(root, fname), "r", encoding="utf-8", errors="ignore") as f:
|
||||
lines = f.readlines()
|
||||
except Exception:
|
||||
continue
|
||||
# 先逐行剔除 // 注释,再拼接扫描,避免误抓注释里的写法
|
||||
clean = []
|
||||
for line in lines:
|
||||
idx = line.find("//")
|
||||
if idx != -1:
|
||||
line = line[:idx]
|
||||
clean.append(line)
|
||||
src = "\n".join(clean)
|
||||
for m in pattern.finditer(src):
|
||||
text = m.group(1)
|
||||
# 还原转义
|
||||
text = text.replace("\\n", "\n").replace("\\t", "\t").replace('\\"', '"')
|
||||
if text and text not in seen:
|
||||
seen.add(text)
|
||||
result.append(text)
|
||||
return result
|
||||
|
||||
|
||||
def get_system_prompts(scanner):
|
||||
"""
|
||||
系统提示统一数据源:合并两类来源并去重:
|
||||
1) GameErrorConst / GameTextConst 等常量文件中的 public const string 值
|
||||
(parse_game_text_const, 含回退到全部常量)。
|
||||
2) 散落在各 .cs 文件中的 event_system_tip 事件触发文本
|
||||
(parse_event_system_tips, 真正弹窗提示文案)。
|
||||
"""
|
||||
const_prompts = parse_game_text_const(scanner.game_text_const, scanner.scripts_dir)
|
||||
event_tips = parse_event_system_tips(scanner.scripts_dir)
|
||||
seen = set()
|
||||
combined = []
|
||||
for t in const_prompts + event_tips:
|
||||
if t and t not in seen:
|
||||
seen.add(t)
|
||||
combined.append(t)
|
||||
return combined
|
||||
|
||||
|
||||
# ================================================================
|
||||
# 五、文档格式化辅助
|
||||
# ================================================================
|
||||
@@ -613,7 +737,8 @@ def build_document(scanner, output_path):
|
||||
print(f" 数据目录: {data_dir} (xlsx {xlsx_n} 个 / csv {csv_n} 个)")
|
||||
|
||||
# 预加载数据
|
||||
system_prompts = parse_game_text_const(scanner.game_text_const, scanner.scripts_dir)
|
||||
# 系统提示 = GameErrorConst/GameTextConst 常量值 合并 event_system_tip 触发文本
|
||||
system_prompts = get_system_prompts(scanner)
|
||||
player_names = get_player_names(scanner)
|
||||
|
||||
print(f" 系统提示: {len(system_prompts)}条")
|
||||
|
||||
Reference in New Issue
Block a user