Files
docxmaker/wenzijiaoben/py/universal_tool.py
T
2026-08-21 10:39:51 +08:00

770 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
通用游戏文字脚本文档生成工具
功能:自动扫描项目目录,识别标准结构,生成游戏文字脚本Word文档。
无需手动配置路径,只需提供项目根目录即可自动完成全部流程。
配置表直接读取 xlsx(优先)或 csv,无需预先转换。
标准目录结构约定:
<project_root>/
├── *_config/ ← 配置表目录 (xlsx/csv)
├── *_game/ ← 游戏客户端目录
│ └── unity/Assets/Resources/.../atlas_ui/ ← 图标资源
│ └── unity/Assets/Scripts/.../*Const*.cs ← 系统提示常量
└── work/ ← 工作目录 (输出等)
正文编号使用 Word 自动编号列表,在 Word 中增删条目后编号会自动重排。
用法:
python universal_tool.py [项目路径] [--output 输出路径]
依赖:pip install openpyxl python-docx
"""
import csv
import os
import re
import sys
import argparse
from docx import Document
from docx.shared import Pt, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
# ============ 常量 ============
CN_NUMBERS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一"]
# 11章节数据源定义(字段名取自表格英文头行第3行;优先读 xlsx,其次 csv)
SECTION_DEFS = {
"quest_texts": {"csv": "quest", "field": "desc", "filter": {"category": ["1", "2"]}},
"achievements": {"csv": "quest", "field": "desc", "filter": {"category": ["9"]}},
"guide_texts": {"csv": "guide", "field": "desc"},
"props": {"csv": "prop", "fields": ["name", "icon", "tips"], "icon_key": "atlas_product_icons"},
"skills": {"csv": "skill", "fields": ["name", "icon", "desc"], "icon_key": "atlas_skill"},
"runes": {"csv": "rune", "fields": ["name", "icon", "desc"], "icon_key": "atlas_rune_icons"},
"talents": {"csv": "playerlevel", "fields": ["name", "icon", "desc"], "icon_key": "atlas_talent_icons", "dedup": "name"},
"equips": {"csv": "equip", "fields": ["name", "icon"], "icon_key": "atlas_equip_icons", "dedup": "name"},
"level_names": {"csv": "fight_sample", "field": "name"},
}
# ================================================================
# 一、项目结构自动扫描
# ================================================================
class ProjectScanner:
"""自动扫描并识别项目目录结构"""
def __init__(self, project_root):
self.root = os.path.abspath(project_root)
self.config_dir = None
self.game_dir = None
self.csv_dir = None
self.work_dir = None
self.resources_dir = None
self.icon_dirs = {}
self.scripts_dir = None
self.game_text_const = None
self.project_name = ""
self._scan()
def _scan(self):
# 1. 查找 _config / _game / work 目录
for entry in os.listdir(self.root):
p = os.path.join(self.root, entry)
if not os.path.isdir(p):
continue
if entry.endswith("_config"):
self.config_dir = p
elif entry.endswith("_game"):
self.game_dir = p
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", "")
# 3. 数据表目录(csv_dir): work/cvs 优先(若存在),否则用 config_dir 本身(含xlsx)
# find_file 优先查找 .xlsx,其次 .csv,两种格式都直接读取
if self.work_dir:
cvs = os.path.join(self.work_dir, "cvs")
if os.path.isdir(cvs) and os.listdir(cvs):
self.csv_dir = cvs
if not self.csv_dir:
self.csv_dir = self.config_dir
# 4. 搜索根目录:优先 Resources_moved,其次 Resources
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):
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:
self.icon_dirs[d] = dp
# 6. Scripts 目录
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
# 7. GameTextConst.cs
if self.scripts_dir:
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
def find_file(self, name):
"""在 csv_dir 或 config_dir 中查找文件(优先 .xlsx,其次 .csv)"""
for d in [self.csv_dir, self.config_dir]:
if not d:
continue
for ext in [".xlsx", ".csv"]:
p = os.path.join(d, name + ext)
if os.path.isfile(p):
return p
return None
def summary(self):
"""打印扫描结果"""
lines = [
f" 项目名称: {self.project_name}",
f" 配置目录: {self.config_dir or '未找到'}",
f" 游戏目录: {self.game_dir or '未找到'}",
f" 数据表目录: {self.csv_dir or '未找到'}",
f" 图标目录: {len(self.icon_dirs)}{list(self.icon_dirs.keys())}",
f" 脚本目录: {self.scripts_dir or '未找到'}",
f" 常量文件: {os.path.basename(self.game_text_const) if self.game_text_const else '未找到'}",
]
return "\n".join(lines)
# ================================================================
# 二、表格数据读取(xlsx/csv 自动识别 + 动态字段名解析)
# ================================================================
def read_table(filepath):
"""
统一表格读取(支持 .xlsx / .csv),返回 (headers, rows)
headers: 第3行英文字段名列表
rows: 第4行起的数据行列表 (list of list)
xlsx 直接读取,无需先转 csv。
"""
ext = os.path.splitext(filepath)[1].lower()
if ext == ".xlsx":
return _read_xlsx(filepath)
return _read_csv(filepath)
def _read_csv(filepath):
"""读取CSV文件,自动检测编码 (utf-8-sig/gbk/gb2312/utf-8)。"""
for enc in ["utf-8-sig", "gbk", "gb2312", "utf-8"]:
try:
with open(filepath, "r", encoding=enc) as f:
all_rows = list(csv.reader(f))
if len(all_rows) < 4:
return all_rows[-1] if all_rows else [], []
return all_rows[2], all_rows[3:]
except (UnicodeDecodeError, UnicodeError):
continue
return [], []
def _read_xlsx(filepath):
"""直接读取 xlsx(无需先转csv),表头结构与csv一致:第3行字段名,第4行起数据。"""
from openpyxl import load_workbook
wb = load_workbook(filepath, read_only=True, data_only=True)
ws = wb.active
all_rows = []
for row in ws.iter_rows(values_only=True):
all_rows.append([str(c) if c is not None else "" for c in row])
wb.close()
if len(all_rows) < 4:
return all_rows[-1] if all_rows else [], []
return all_rows[2], all_rows[3:]
def field_map(headers):
"""将英文头行转为 {字段名: 列索引} 映射"""
return {h.strip(): i for i, h in enumerate(headers) if h.strip()}
def field_val(row, fm, name, default=""):
"""从数据行中按字段名取值"""
idx = fm.get(name)
if idx is not None and idx < len(row):
return row[idx].strip()
return default
# ================================================================
# 三、图片资源查找
# ================================================================
def find_image(icon_name, icon_dirs, icon_key):
"""根据 icon 名称在指定目录中查找图片文件"""
icon_dir = icon_dirs.get(icon_key)
if not icon_dir or not os.path.isdir(icon_dir):
return None
# 直接匹配
for ext in [".png", ".jpg"]:
p = os.path.join(icon_dir, icon_name + ext)
if os.path.isfile(p):
return p
# 补零匹配 (如 icon_product_1 -> icon_product_01)
m = re.match(r"^(.+?)(\d+)$", icon_name)
if m:
prefix, num = m.group(1), m.group(2)
for pad in [2, 3]:
padded = prefix + num.zfill(pad)
for ext in [".png", ".jpg"]:
p = os.path.join(icon_dir, padded + ext)
if os.path.isfile(p):
return p
return None
# ================================================================
# 四、GameTextConst.cs 自动解析
# ================================================================
def parse_game_text_const(cs_path, scripts_dir):
"""
自动解析 GameTextConst.cs 中所有 public const string 定义,
扫描项目中其他 .cs 文件确认哪些常量被引用,
返回去重后的文本列表。
"""
if not cs_path or not os.path.isfile(cs_path):
return []
# 读取文件
content = None
for enc in ["utf-8-sig", "utf-8", "gbk", "gb2312"]:
try:
with open(cs_path, "r", encoding=enc) as f:
content = f.read()
break
except (UnicodeDecodeError, UnicodeError):
continue
if content is None:
return []
# 提取常量:public const string Name = "value";
pattern = r'public\s+const\s+string\s+(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"\s*;'
all_consts = {}
for m in re.finditer(pattern, content):
name, value = m.group(1), m.group(2)
# 还原转义
value = value.replace("\\n", "\n").replace("\\t", "\t").replace('\\"', '"')
all_consts[name] = value
if not all_consts:
return []
# 扫描引用
const_names = set(all_consts.keys())
referenced = set()
cs_file = os.path.basename(cs_path)
if scripts_dir and os.path.isdir(scripts_dir):
for root, _, files in os.walk(scripts_dir):
for fname in files:
if not fname.endswith(".cs") or fname == cs_file:
continue
try:
with open(os.path.join(root, fname), "r", encoding="utf-8", errors="ignore") as f:
src = f.read()
for cn in const_names:
if cn in src:
referenced.add(cn)
except Exception:
continue
# 去重文本值
seen = set()
result = []
for name, text in all_consts.items():
if name in referenced and text not in seen:
seen.add(text)
result.append(text)
return result
# ================================================================
# 五、文档格式化辅助
# ================================================================
def _run(para, text, font="仿宋", size=14, bold=False, underline=None):
"""创建带格式的 run"""
r = para.add_run(text)
r.font.name = font
r._element.rPr.rFonts.set(qn("w:eastAsia"), font)
r.font.size = Pt(size)
r.font.bold = bold
if underline is not None:
r.font.underline = underline
return r
def add_title(doc, text):
"""标题:宋体 二号(22pt) 加粗 居中"""
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
_run(p, text, font="宋体", size=22, bold=True, underline=False)
def add_section_heading(doc, cn_num, title):
"""小标题:仿宋 四号(14pt) 加粗 中文编号"""
p = doc.add_paragraph()
_run(p, f"{cn_num}{title}", font="仿宋", size=14, bold=True)
# ---- Word 自动编号辅助 ----
# 用 Word 的 numbering 机制生成 1. 2. 3. ... 列表,
# 这样在 Word 中增删条目后编号会自动重排,无需手改数字。
def _get_numbering_element(doc):
"""获取文档的 <w:numbering> 根元素(默认模板已含 numbering part)。"""
try:
return doc.part.numbering_part.element
except KeyError:
raise RuntimeError("当前文档模板缺少 numbering part,无法使用自动编号。")
def _create_decimal_numbering(doc):
"""
新建一个十进制自动编号定义 (1. 2. 3. ...),返回新的 numId。
每次调用返回独立的 numId,编号从 1 开始,
因此每个章节调用一次即可获得独立计数。
"""
numbering = _get_numbering_element(doc)
abs_ids = [int(an.get(qn('w:abstractNumId'))) for an in numbering.findall(qn('w:abstractNum'))]
num_ids = [int(n.get(qn('w:numId'))) for n in numbering.findall(qn('w:num'))]
next_abs = (max(abs_ids) + 1) if abs_ids else 0
next_num = (max(num_ids) + 1) if num_ids else 1
# <w:abstractNum>
abstract_num = OxmlElement('w:abstractNum')
abstract_num.set(qn('w:abstractNumId'), str(next_abs))
multi_level = OxmlElement('w:multiLevelType')
multi_level.set(qn('w:val'), 'singleLevel')
abstract_num.append(multi_level)
lvl = OxmlElement('w:lvl')
lvl.set(qn('w:ilvl'), '0')
start = OxmlElement('w:start')
start.set(qn('w:val'), '1')
lvl.append(start)
num_fmt = OxmlElement('w:numFmt')
num_fmt.set(qn('w:val'), 'decimal')
lvl.append(num_fmt)
lvl_text = OxmlElement('w:lvlText')
lvl_text.set(qn('w:val'), '%1.')
lvl.append(lvl_text)
lvl_jc = OxmlElement('w:lvlJc')
lvl_jc.set(qn('w:val'), 'left')
lvl.append(lvl_jc)
p_pr = OxmlElement('w:pPr')
ind = OxmlElement('w:ind')
ind.set(qn('w:left'), '420')
ind.set(qn('w:hanging'), '420')
p_pr.append(ind)
lvl.append(p_pr)
abstract_num.append(lvl)
# <w:num>
num = OxmlElement('w:num')
num.set(qn('w:numId'), str(next_num))
abs_ref = OxmlElement('w:abstractNumId')
abs_ref.set(qn('w:val'), str(next_abs))
num.append(abs_ref)
# abstractNum 必须排在所有 num 之前
last_abs = numbering.findall(qn('w:abstractNum'))
if last_abs:
last_abs[-1].addnext(abstract_num)
else:
numbering.insert(0, abstract_num)
numbering.append(num)
return next_num
def _apply_numbering(paragraph, num_id, ilvl=0):
"""给段落应用自动编号 (设置 numPr)。"""
p_pr = paragraph._p.get_or_add_pPr()
for existing in p_pr.findall(qn('w:numPr')):
p_pr.remove(existing)
num_pr = OxmlElement('w:numPr')
ilvl_el = OxmlElement('w:ilvl')
ilvl_el.set(qn('w:val'), str(ilvl))
num_pr.append(ilvl_el)
num_id_el = OxmlElement('w:numId')
num_id_el.set(qn('w:val'), str(num_id))
num_pr.append(num_id_el)
p_pr.append(num_pr)
def add_numbered_items(doc, items):
"""
编号列表:仿宋 四号 不加粗,使用 Word 自动编号。
每次调用创建独立编号序列(从 1 开始),
在 Word 中增删条目后编号会自动重排。
"""
if not items:
return
num_id = _create_decimal_numbering(doc)
for text in items:
p = doc.add_paragraph()
_apply_numbering(p, num_id)
_run(p, str(text), font="仿宋", size=14, bold=False)
def _set_cell(cell, text, bold=False, size=14):
"""设置表格单元格文本"""
cell.text = ""
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
_run(p, str(text), font="仿宋", size=size, bold=bold)
def _insert_img(cell, img_path):
"""在单元格中插入图片(高度3cm 锁定纵横比)"""
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
p.add_run().add_picture(img_path, height=Cm(3))
def add_data_table(doc, headers, rows, icon_dirs=None, icon_key=None):
"""通用数据表格(支持图片列)"""
table = doc.add_table(rows=1, cols=len(headers), style="Table Grid")
table.alignment = WD_TABLE_ALIGNMENT.CENTER
for i, h in enumerate(headers):
_set_cell(table.rows[0].cells[i], h, bold=True, size=10)
for rd in rows:
row = table.add_row()
_set_cell(row.cells[0], rd[0])
if len(rd) > 1:
icon_name = rd[1]
img = find_image(icon_name, icon_dirs, icon_key) if icon_name and icon_dirs else None
if img:
_insert_img(row.cells[1], img)
else:
_set_cell(row.cells[1], "", size=10)
if len(rd) > 2:
_set_cell(row.cells[2], rd[2], size=10)
def add_name_table(doc, names):
"""玩家姓名表格(姓/名 两列)"""
table = doc.add_table(rows=1, cols=2, style="Table Grid")
table.alignment = WD_TABLE_ALIGNMENT.CENTER
for i, h in enumerate(["姓", "名"]):
_set_cell(table.rows[0].cells[i], h, bold=True)
for surname, given in names:
row = table.add_row()
_set_cell(row.cells[0], surname)
_set_cell(row.cells[1], given)
# ================================================================
# 六、数据提取函数
# ================================================================
def get_player_names(scanner):
"""获取玩家随机名称(优先 name.xlsx/csv,否则使用默认列表)"""
fp = scanner.find_file("name")
if fp:
headers, rows = read_table(fp)
fm = field_map(headers)
if "first" in fm and "last" in fm:
seen = set()
result = []
for r in rows:
first = field_val(r, fm, "first")
last = field_val(r, fm, "last")
if first and last:
key = (first, last)
if key not in seen:
seen.add(key)
result.append(key)
if result:
return result
# 默认名称(100对)
return [
("李", "子轩"), ("王", "梓涵"), ("张", "浩然"), ("刘", "欣怡"),
("陈", "宇轩"), ("杨", "晨曦"), ("赵", "俊杰"), ("黄", "雅静"),
("周", "天佑"), ("吴", "梦琪"), ("徐", "博文"), ("孙", "思彤"),
("胡", "睿智"), ("朱", "静怡"), ("高", "昊天"), ("林", "雨桐"),
("何", "泽宇"), ("郭", "婉清"), ("马", "俊熙"), ("罗", "雪柔"),
("梁", "子豪"), ("宋", "依琳"), ("郑", "宇航"), ("谢", "诗涵"),
("韩", "志强"), ("唐", "梦瑶"), ("冯", "文博"), ("于", "雅楠"),
("董", "天宇"), ("萧", "欣妍"), ("程", "子睿"), ("曹", "静雯"),
("袁", "俊豪"), ("邓", "雨欣"), ("许", "浩宇"), ("傅", "语嫣"),
("沈", "子涵"), ("曾", "思雨"), ("彭", "博涛"), ("吕", "雅婷"),
("苏", "天翔"), ("卢", "梦洁"), ("蒋", "文轩"), ("蔡", "雪梅"),
("贾", "志远"), ("丁", "依娜"), ("魏", "子安"), ("薛", "静雅"),
("叶", "俊峰"), ("阎", "雨婷"), ("余", "浩轩"), ("潘", "诗琪"),
("杜", "子恒"), ("戴", "思雅"), ("夏", "博超"), ("钟", "雅琪"),
("汪", "天瑞"), ("田", "梦菲"), ("任", "文昊"), ("姜", "雪莲"),
("范", "志鹏"), ("方", "依婷"), ("石", "子健"), ("姚", "静淑"),
("谭", "俊凯"), ("廖", "雨菲"), ("邹", "浩南"), ("熊", "诗瑶"),
("金", "子宁"), ("陆", "思琪"), ("郝", "博远"), ("孔", "雅静"),
("白", "天浩"), ("崔", "梦婷"), ("康", "文杰"), ("毛", "雪琪"),
("邱", "志伟"), ("秦", "依静"), ("江", "子文"), ("史", "静怡"),
("顾", "俊杰"), ("侯", "雨萱"), ("邵", "浩天"), ("孟", "诗涵"),
("龙", "子辰"), ("万", "思妍"), ("段", "博文"), ("雷", "雅欣"),
("钱", "天佑"), ("汤", "梦瑶"), ("尹", "文豪"), ("黎", "雪莹"),
("易", "志强"), ("常", "依琳"), ("武", "子轩"), ("乔", "静雯"),
("贺", "俊熙"), ("赖", "雨桐"), ("龚", "浩宇"), ("文", "诗琪"),
]
def get_csv_list_data(scanner, csv_name, field_name, filter_dict=None):
"""通用:从表格(xlsx/csv)中按字段名提取去重文本列表"""
fp = scanner.find_file(csv_name)
if not fp:
return []
headers, rows = read_table(fp)
fm = field_map(headers)
seen = set()
result = []
for r in rows:
# 过滤条件
if filter_dict:
ok = True
for fk, fvs in filter_dict.items():
if field_val(r, fm, fk) not in fvs:
ok = False
break
if not ok:
continue
val = field_val(r, fm, field_name)
if val and val not in seen:
seen.add(val)
result.append(val)
return result
def get_csv_table_data(scanner, csv_name, field_names, dedup_field=None):
"""通用:从表格(xlsx/csv)中提取多字段表格数据 [(f1, f2, f3, ...), ...]"""
fp = scanner.find_file(csv_name)
if not fp:
return []
headers, rows = read_table(fp)
fm = field_map(headers)
seen = set()
result = []
for r in rows:
vals = tuple(field_val(r, fm, fn) for fn in field_names)
if not vals[0]:
continue
if dedup_field:
key = vals[0]
if key in seen:
continue
seen.add(key)
result.append(vals)
return result
# ================================================================
# 七、文档构建主流程
# ================================================================
def build_document(scanner, output_path):
"""根据扫描结果构建完整文档"""
print("\n[1/4] 准备数据源...")
# 直接从 config_dir / csv_dir 读取 xlsx(优先) 或 csv,不再转换。
data_dir = scanner.csv_dir or scanner.config_dir
if data_dir:
files = [f for f in os.listdir(data_dir) if not f.startswith("~$")]
xlsx_n = sum(1 for f in files if f.endswith(".xlsx"))
csv_n = sum(1 for f in files if f.endswith(".csv"))
print(f" 数据目录: {data_dir} (xlsx {xlsx_n} 个 / csv {csv_n} 个)")
# 预加载数据
system_prompts = parse_game_text_const(scanner.game_text_const, scanner.scripts_dir)
player_names = get_player_names(scanner)
print(f" 系统提示: {len(system_prompts)}条")
print(f" 玩家名称: {len(player_names)}对")
print("\n[2/4] 创建文档...")
doc = Document()
style = doc.styles["Normal"]
style.font.name = "仿宋"
style.font.size = Pt(14)
style._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋")
doc_title = f"《{scanner.project_name}》文字脚本" if scanner.project_name else "游戏文字脚本"
add_title(doc, doc_title)
print("\n[3/4] 写入章节...")
icon_dirs = scanner.icon_dirs
sections_done = 0
# --- 一、系统提示 ---
add_section_heading(doc, CN_NUMBERS[0], "系统提示")
add_numbered_items(doc, system_prompts)
print(f" 一、系统提示: {len(system_prompts)}条")
sections_done += 1
# --- 二、任务文本 ---
quest_def = SECTION_DEFS["quest_texts"]
quest_texts = get_csv_list_data(scanner, quest_def["csv"], quest_def["field"], quest_def.get("filter"))
add_section_heading(doc, CN_NUMBERS[1], "任务文本")
add_numbered_items(doc, quest_texts)
print(f" 二、任务文本: {len(quest_texts)}条")
sections_done += 1
# --- 三、成就文本 ---
ach_def = SECTION_DEFS["achievements"]
ach_texts = get_csv_list_data(scanner, ach_def["csv"], ach_def["field"], ach_def.get("filter"))
add_section_heading(doc, CN_NUMBERS[2], "成就文本")
add_numbered_items(doc, ach_texts)
print(f" 三、成就文本: {len(ach_texts)}条")
sections_done += 1
# --- 四、玩家随机名称文本 ---
add_section_heading(doc, CN_NUMBERS[3], "玩家随机名称文本")
add_name_table(doc, player_names)
print(f" 四、玩家随机名称文本: {len(player_names)}对")
sections_done += 1
# --- 五、新手指引文本 ---
guide_def = SECTION_DEFS["guide_texts"]
guide_texts = get_csv_list_data(scanner, guide_def["csv"], guide_def["field"])
add_section_heading(doc, CN_NUMBERS[4], "新手指引文本")
add_numbered_items(doc, guide_texts)
print(f" 五、新手指引文本: {len(guide_texts)}条")
sections_done += 1
# --- 六、道具说明 ---
prop_def = SECTION_DEFS["props"]
prop_data = get_csv_table_data(scanner, prop_def["csv"], prop_def["fields"])
add_section_heading(doc, CN_NUMBERS[5], "道具说明")
add_data_table(doc, ["道具名称", "图标", "道具描述"], prop_data, icon_dirs, prop_def["icon_key"])
print(f" 六、道具说明: {len(prop_data)}条")
sections_done += 1
# --- 七、技能 ---
skill_def = SECTION_DEFS["skills"]
skill_data = get_csv_table_data(scanner, skill_def["csv"], skill_def["fields"])
add_section_heading(doc, CN_NUMBERS[6], "技能")
add_data_table(doc, ["技能名称", "技能图标", "技能描述"], skill_data, icon_dirs, skill_def["icon_key"])
print(f" 七、技能: {len(skill_data)}条")
sections_done += 1
# --- 八、灵石 ---
rune_def = SECTION_DEFS["runes"]
rune_data = get_csv_table_data(scanner, rune_def["csv"], rune_def["fields"])
add_section_heading(doc, CN_NUMBERS[7], "灵石")
add_data_table(doc, ["灵石名称", "灵石图标", "灵石介绍"], rune_data, icon_dirs, rune_def["icon_key"])
print(f" 八、灵石: {len(rune_data)}条")
sections_done += 1
# --- 九、天赋 ---
talent_def = SECTION_DEFS["talents"]
talent_data = get_csv_table_data(scanner, talent_def["csv"], talent_def["fields"], talent_def.get("dedup"))
add_section_heading(doc, CN_NUMBERS[8], "天赋")
add_data_table(doc, ["天赋名称", "天赋图标", "天赋介绍"], talent_data, icon_dirs, talent_def["icon_key"])
print(f" 九、天赋: {len(talent_data)}条")
sections_done += 1
# --- 十、装备 ---
equip_def = SECTION_DEFS["equips"]
equip_data = get_csv_table_data(scanner, equip_def["csv"], equip_def["fields"], equip_def.get("dedup"))
add_section_heading(doc, CN_NUMBERS[9], "装备")
add_data_table(doc, ["装备名称", "装备图标"], equip_data, icon_dirs, equip_def["icon_key"])
print(f" 十、装备: {len(equip_data)}条")
sections_done += 1
# --- 十一、关卡名称 ---
level_def = SECTION_DEFS["level_names"]
level_names = get_csv_list_data(scanner, level_def["csv"], level_def["field"])
add_section_heading(doc, CN_NUMBERS[10], "关卡名称")
add_numbered_items(doc, level_names)
print(f" 十一、关卡名称: {len(level_names)}条")
sections_done += 1
print(f"\n[4/4] 保存文档...")
doc.save(output_path)
print(f" 文档已生成: {output_path}")
print(f" 共 {sections_done} 个章节")
# ================================================================
# 主入口
# ================================================================
def main():
parser = argparse.ArgumentParser(description="通用游戏文字脚本文档生成工具")
parser.add_argument("project_path", nargs="?", help="项目根目录路径 (默认: 当前目录)")
parser.add_argument("--output", "-o", help="输出文档路径 (默认: 自动生成)")
args = parser.parse_args()
project_path = args.project_path or os.getcwd()
if not os.path.isdir(project_path):
print(f"错误: 目录不存在 - {project_path}")
sys.exit(1)
print(f"{'='*50}")
print(f" 通用游戏文字脚本生成工具")
print(f"{'='*50}")
print(f"\n扫描项目: {project_path}")
scanner = ProjectScanner(project_path)
print(f"\n{scanner.summary()}")
if not scanner.csv_dir and not scanner.config_dir:
print("\n错误: 未找到配置目录 (*_config) 或CSV目录")
sys.exit(1)
if args.output:
output_path = args.output
else:
work_dir = scanner.work_dir or os.path.join(project_path, "work")
os.makedirs(work_dir, exist_ok=True)
title = scanner.project_name or "游戏"
output_path = os.path.join(work_dir, f"《{title}》游戏文字脚本.docx")
build_document(scanner, output_path)
print(f"\n{'='*50}")
print(f" 全部完成!")
print(f"{'='*50}")
if __name__ == "__main__":
main()