更新
This commit is contained in:
@@ -84,10 +84,34 @@ class ProjectScanner:
|
|||||||
elif entry == "work":
|
elif entry == "work":
|
||||||
self.work_dir = p
|
self.work_dir = p
|
||||||
|
|
||||||
# 2. 项目名
|
# 1b. 配置目录兜底:部分项目(如 HM6)把配置表放在 P-XXX_game/config
|
||||||
if self.config_dir:
|
# 而非独立的 *_config 目录。优先顶层 config 目录,其次游戏目录下的 config 子目录,
|
||||||
base = os.path.basename(self.config_dir)
|
# 取其中确实含 xlsx/csv 的那个作为 config_dir。
|
||||||
self.project_name = base.replace("_config", "")
|
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)
|
# 3. 数据表目录(csv_dir): work/cvs 优先(若存在),否则用 config_dir 本身(含xlsx)
|
||||||
# find_file 优先查找 .xlsx,其次 .csv,两种格式都直接读取
|
# find_file 优先查找 .xlsx,其次 .csv,两种格式都直接读取
|
||||||
@@ -98,49 +122,79 @@ class ProjectScanner:
|
|||||||
if not self.csv_dir:
|
if not self.csv_dir:
|
||||||
self.csv_dir = self.config_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:
|
if self.game_dir:
|
||||||
for root, dirs, _ in os.walk(self.game_dir):
|
for root, dirs, _ in os.walk(self.game_dir):
|
||||||
for name in ["Resources_moved", "Resources"]:
|
if os.path.basename(root) == "atlas_ui":
|
||||||
if name in dirs:
|
self.resources_dir = os.path.dirname(root)
|
||||||
self.resources_dir = os.path.join(root, name)
|
for d in dirs:
|
||||||
break
|
if d.startswith("atlas_"):
|
||||||
if self.resources_dir:
|
dp = os.path.join(root, d)
|
||||||
break
|
if any(f.lower().endswith((".png", ".jpg", ".jpeg"))
|
||||||
|
for f in os.listdir(dp)):
|
||||||
|
self.icon_dirs[d] = dp
|
||||||
|
break # 找到第一个 atlas_ui 即止
|
||||||
|
|
||||||
# 5. 图标目录 (递归搜索 atlas_* 子目录,只保留包含图片的叶子目录)
|
# 6. Scripts 目录: 递归收集所有名为 Scripts 的目录,挑选"真实游戏脚本"目录。
|
||||||
if self.resources_dir:
|
# 规则:优先"直接位于 Assets 目录下"的 Scripts(Unity 游戏源码根目录约定,
|
||||||
for root, dirs, files in os.walk(self.resources_dir):
|
# 如 unity/Assets/Scripts 或 Assets/Scripts),排除 TutorialInfo/示例等非源码目录;
|
||||||
for d in dirs:
|
# 在其中再优先含游戏常量文件(GameError/GameText/Error/Text/Event/Msg Const)者;
|
||||||
if d.startswith("atlas_"):
|
# 无 Assets/Scripts 时回退到全部候选中"含游戏常量"或".cs 最多"的目录。
|
||||||
dp = os.path.join(root, d)
|
# 同时跳过 Library(Unity 包缓存, 文件巨多且非游戏源码),避免误选并加速。
|
||||||
# 检查是否包含图片文件
|
|
||||||
has_img = any(f.lower().endswith((".png", ".jpg", ".jpeg")) for f in os.listdir(dp))
|
|
||||||
if has_img:
|
|
||||||
self.icon_dirs[d] = dp
|
|
||||||
|
|
||||||
# 6. Scripts 目录
|
|
||||||
if self.game_dir:
|
if self.game_dir:
|
||||||
unity = os.path.join(self.game_dir, "unity")
|
all_scripts = []
|
||||||
assets = os.path.join(unity, "Assets") if os.path.isdir(unity) else None
|
for root, dirs, _ in os.walk(self.game_dir):
|
||||||
if assets:
|
if "Library" in dirs:
|
||||||
scripts = os.path.join(assets, "Scripts")
|
dirs.remove("Library") # 不进入 Unity 包缓存
|
||||||
if os.path.isdir(scripts):
|
if os.path.basename(root) == "Scripts":
|
||||||
self.scripts_dir = 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:
|
if self.scripts_dir:
|
||||||
|
const_files = []
|
||||||
for root, _, files in os.walk(self.scripts_dir):
|
for root, _, files in os.walk(self.scripts_dir):
|
||||||
for f in files:
|
for f in files:
|
||||||
if f.lower().endswith(".cs") and "const" in f.lower() and ("text" in f.lower() or "game" in f.lower()):
|
if f.endswith(".cs") and "Const" in f:
|
||||||
self.game_text_const = os.path.join(root, f)
|
const_files.append(os.path.join(root, f))
|
||||||
return
|
if const_files:
|
||||||
# 宽松匹配:任意 *Const*.cs
|
def _pick(sub):
|
||||||
for root, _, files in os.walk(self.scripts_dir):
|
for p in const_files:
|
||||||
for f in files:
|
if sub in os.path.basename(p):
|
||||||
if "Const" in f and f.endswith(".cs"):
|
return p
|
||||||
self.game_text_const = os.path.join(root, f)
|
return None
|
||||||
return
|
self.game_text_const = (
|
||||||
|
_pick("GameErrorConst") or _pick("ErrorConst")
|
||||||
|
or _pick("GameTextConst") or _pick("TextConst")
|
||||||
|
or const_files[0]
|
||||||
|
)
|
||||||
|
|
||||||
def find_file(self, name):
|
def find_file(self, name):
|
||||||
"""在 csv_dir 或 config_dir 中查找文件(优先 .xlsx,其次 .csv)"""
|
"""在 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):
|
def parse_game_text_const(cs_path, scripts_dir):
|
||||||
@@ -308,15 +362,85 @@ def parse_game_text_const(cs_path, scripts_dir):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 去重文本值
|
# 去重文本值
|
||||||
|
# 优先保留"被其他源码引用"的常量;若引用扫描未命中(项目引用方式不同,如 L17 把错误码集中管理),
|
||||||
|
# 则回退为使用常量文件中的全部 public const string,避免整章变空。
|
||||||
seen = set()
|
seen = set()
|
||||||
result = []
|
result = []
|
||||||
for name, text in all_consts.items():
|
src_items = ((n, t) for n, t in all_consts.items() if n in referenced) if referenced else all_consts.items()
|
||||||
if name in referenced and text not in seen:
|
for name, text in src_items:
|
||||||
|
if text not in seen:
|
||||||
seen.add(text)
|
seen.add(text)
|
||||||
result.append(text)
|
result.append(text)
|
||||||
return result
|
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} 个)")
|
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)
|
player_names = get_player_names(scanner)
|
||||||
|
|
||||||
print(f" 系统提示: {len(system_prompts)}条")
|
print(f" 系统提示: {len(system_prompts)}条")
|
||||||
|
|||||||
Reference in New Issue
Block a user