commit c0dfa441a5fda84b3e9e4c584d4bfe1b6291e3dd Author: JA <1323160571@qq.com> Date: Mon Jun 8 10:49:52 2026 +0800 文字脚本自动生成 diff --git a/wenzijiaoben/memories/data_source_mapping.json b/wenzijiaoben/memories/data_source_mapping.json new file mode 100644 index 0000000..62193be --- /dev/null +++ b/wenzijiaoben/memories/data_source_mapping.json @@ -0,0 +1,108 @@ +{ + "description": "CSV数据源到文档章节的映射关系", + "csv_base_dir": "d:\\p-L12\\work\\cvs", + "mappings": { + "system_prompts": { + "source": "GameTextConst.cs (代码引用分析)", + "section": "一、系统提示", + "field": "常量值文本", + "filter": "仅保留在代码中被实际引用的常量(排除仅在注释中出现的)", + "dedup": "按文本值去重" + }, + "quest_texts": { + "source": "quest.csv", + "section": "二、任务文本", + "field": "desc (第4列, index=3)", + "filter": "category == 1 或 category == 2", + "dedup": "按desc文本去重" + }, + "achievement_texts": { + "source": "quest.csv", + "section": "三、成就文本", + "field": "desc (第4列, index=3)", + "filter": "category == 9", + "dedup": "按desc文本去重" + }, + "player_names": { + "source": "原始文档固定数据 (100对姓名)", + "section": "四、玩家随机名称文本", + "format": "表格: 姓 | 名" + }, + "guide_texts": { + "source": "guide.csv", + "section": "五、新手指引文本", + "field": "desc (第8列, index=7)", + "filter": "无", + "dedup": "按desc文本去重" + }, + "props": { + "source": "prop.csv", + "section": "六、道具说明", + "fields": { + "name": "第3列 (index=2)", + "tips": "第7列 (index=6)", + "icon": "第8列 (index=7)" + }, + "icon_dir": "atlas_product_icons", + "icon_naming": "icon_product_XX.png (XX为补零两位数, 如 icon_product_01)" + }, + "skills": { + "source": "skill.csv", + "section": "七、技能", + "fields": { + "name": "第2列 (index=1)", + "icon": "第7列 (index=6)", + "desc": "第22列 (index=21)" + }, + "icon_dir": "atlas_skill", + "icon_naming": "skillX.png (X为数字, 不补零, 如 skill1, skill10)" + }, + "runes": { + "source": "rune.csv", + "section": "八、灵石", + "fields": { + "name": "第2列 (index=1)", + "icon": "第3列 (index=2)", + "desc": "第9列 (index=8)" + }, + "icon_dir": "atlas_rune_icons", + "icon_naming": "rune_icon_X.png (X为数字, 不补零)" + }, + "talents": { + "source": "playerlevel.csv", + "section": "九、天赋", + "fields": { + "name": "第14列 (index=13)", + "desc": "第15列 (index=14)", + "icon": "第16列 (index=15)" + }, + "icon_dir": "atlas_talent_icons", + "icon_naming": "icon_talent_X.png (X为数字, 不补零)", + "dedup": "按name去重" + }, + "equipment": { + "source": "equip.csv", + "section": "十、装备", + "fields": { + "name": "第2列 (index=1)", + "icon": "第6列 (index=5)" + }, + "icon_dir": "atlas_equip_icons", + "icon_naming": "equip_icon_X.png (X为数字, 不补零)", + "dedup": "按name去重", + "note": "equip.csv无desc字段" + }, + "level_names": { + "source": "fight_sample.csv", + "section": "十一、关卡名称", + "field": "name (第3列, index=2)", + "filter": "无", + "dedup": "按name去重" + } + }, + "csv_format": { + "header_rows": 3, + "header_structure": "第1行:中文头, 第2行:类型定义, 第3行:英文字段名", + "encoding_note": "部分文件为GBK编码,需自动检测" + } +} diff --git a/wenzijiaoben/memories/doc_format_spec.json b/wenzijiaoben/memories/doc_format_spec.json new file mode 100644 index 0000000..185edd1 --- /dev/null +++ b/wenzijiaoben/memories/doc_format_spec.json @@ -0,0 +1,52 @@ +{ + "document_title": "《不朽封仙》文字脚本", + "sections": [ + {"number": "一", "title": "系统提示", "type": "numbered_list"}, + {"number": "二", "title": "任务文本", "type": "numbered_list"}, + {"number": "三", "title": "成就文本", "type": "numbered_list"}, + {"number": "四", "title": "玩家随机名称文本", "type": "table", "columns": ["姓", "名"]}, + {"number": "五", "title": "新手指引文本", "type": "numbered_list"}, + {"number": "六", "title": "道具说明", "type": "table", "columns": ["道具名称", "图标", "道具描述"]}, + {"number": "七", "title": "技能", "type": "table", "columns": ["技能名称", "技能图标", "技能描述"]}, + {"number": "八", "title": "灵石", "type": "table", "columns": ["灵石名称", "灵石图标", "灵石介绍"]}, + {"number": "九", "title": "天赋", "type": "table", "columns": ["天赋名称", "天赋图标", "天赋介绍"]}, + {"number": "十", "title": "装备", "type": "table", "columns": ["装备名称", "装备图标"]}, + {"number": "十一", "title": "关卡名称", "type": "numbered_list"} + ], + "format_rules": { + "title": { + "font": "宋体", + "font_en": "SimSun", + "size": "二号 (22pt)", + "bold": true, + "underline": false, + "color": "黑色 (无蓝色)", + "alignment": "居中" + }, + "section_heading": { + "font": "仿宋", + "font_en": "FangSong", + "size": "四号 (14pt)", + "bold": true, + "numbering": "中文数字 (一、二、三...十一)" + }, + "text_content": { + "font": "仿宋", + "font_en": "FangSong", + "size": "四号 (14pt)", + "bold": false, + "numbering": "阿拉伯数字 (1. 2. 3. ...)" + }, + "table_images": { + "height": "3cm", + "aspect_ratio": "锁定纵横比", + "missing_image": "留空" + } + }, + "dedup_rules": { + "general": "所有文本和表格内容不得出现重复项", + "system_prompts": "GameTextConst中相同文本值的常量只保留第一个", + "talents": "天赋按name去重(同一天赋在不同等级重复出现)", + "equipment": "装备按name去重(同一装备不同品质重复出现)" + } +} diff --git a/wenzijiaoben/memories/icon_resource_mapping.json b/wenzijiaoben/memories/icon_resource_mapping.json new file mode 100644 index 0000000..3640e3e --- /dev/null +++ b/wenzijiaoben/memories/icon_resource_mapping.json @@ -0,0 +1,52 @@ +{ + "description": "图标资源名称到图片文件的映射规则与目录结构", + "base_dir": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Resources\\Resources_moved\\gui\\atlas_ui", + "directories": { + "atlas_product_icons": { + "section": "六、道具说明", + "csv_field": "prop.csv icon (index=7)", + "naming_pattern": "icon_product_XX.png", + "naming_note": "XX为补零两位数 (01-63), 如 icon_product_1 -> icon_product_01.png", + "file_count": 63, + "missing_icons": [] + }, + "atlas_skill": { + "section": "七、技能", + "csv_field": "skill.csv icon (index=6)", + "naming_pattern": "skillX.png", + "naming_note": "X为数字不补零 (1-99), 如 skill1, skill10, skill99", + "file_count": 99, + "missing_icons": [] + }, + "atlas_rune_icons": { + "section": "八、灵石", + "csv_field": "rune.csv icon (index=2)", + "naming_pattern": "rune_icon_X.png", + "naming_note": "X为数字不补零 (1-18)", + "file_count": 12, + "missing_icons": ["rune_icon_13", "rune_icon_14", "rune_icon_15", "rune_icon_16", "rune_icon_17", "rune_icon_18"] + }, + "atlas_talent_icons": { + "section": "九、天赋", + "csv_field": "playerlevel.csv icon (index=15)", + "naming_pattern": "icon_talent_X.png", + "naming_note": "X为数字不补零 (1-6)", + "file_count": 6, + "missing_icons": [] + }, + "atlas_equip_icons": { + "section": "十、装备", + "csv_field": "equip.csv icon (index=5)", + "naming_pattern": "equip_icon_X.png", + "naming_note": "X为数字不补零 (1-6)", + "file_count": 6, + "missing_icons": [] + } + }, + "image_lookup_strategy": "先直接匹配 icon_name.png,若失败则对末尾数字补零至2位或3位重试", + "image_insert_rules": { + "height": "3cm", + "aspect_ratio": "锁定纵横比", + "missing_image_action": "留空单元格" + } +} diff --git a/wenzijiaoben/memories/project_config.json b/wenzijiaoben/memories/project_config.json new file mode 100644 index 0000000..ffe1ee8 --- /dev/null +++ b/wenzijiaoben/memories/project_config.json @@ -0,0 +1,33 @@ +{ + "project_name": "P-L12《不朽封仙》", + "workspace_root": "d:\\p-L12", + "paths": { + "config_dir": "d:\\p-L12\\P-L12_config", + "game_dir": "d:\\p-L12\\P-L12_game", + "csv_dir": "d:\\p-L12\\work\\cvs", + "scripts_dir": "d:\\p-L12\\work\\py", + "memories_dir": "d:\\p-L12\\work\\memories", + "output_doc": "d:\\p-L12\\work\\《不朽封仙》游戏文字脚本.docx" + }, + "scripts": { + "xlsx_to_csv": "d:\\p-L12\\work\\py\\xlsx_to_csv.py", + "generate_doc": "d:\\p-L12\\work\\py\\generate_doc.py" + }, + "dependencies": { + "python_packages": ["openpyxl", "python-docx"], + "install_commands": [ + "pip install openpyxl", + "pip install python-docx" + ] + }, + "csv_encoding": { + "note": "部分CSV文件使用GBK/GB2312编码,非UTF-8", + "read_strategy": "按优先级尝试 utf-8-sig -> gbk -> gb2312 -> utf-8", + "write_encoding": "xlsx_to_csv.py 输出使用 UTF-8 with BOM (utf-8-sig)" + }, + "game_code_paths": { + "game_text_const": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Scripts\\mgame\\error\\GameTextConst.cs", + "scripts_root": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Scripts" + }, + "icon_resource_base": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Resources\\Resources_moved\\gui\\atlas_ui" +} diff --git a/wenzijiaoben/memories/system_prompts.json b/wenzijiaoben/memories/system_prompts.json new file mode 100644 index 0000000..179d32d --- /dev/null +++ b/wenzijiaoben/memories/system_prompts.json @@ -0,0 +1,104 @@ +{ + "description": "GameTextConst.cs 中被代码实际引用的系统提示常量", + "source_file": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Scripts\\mgame\\error\\GameTextConst.cs", + "total_referenced": 85, + "total_unique_text": 83, + "note": "85个被引用常量中,有2对文本值重复('{0}不足。' 和 '保存失败。'),去重后为83条", + "referenced_constants": { + "ErrorAlreadyClaimed": "已领取。", + "ErrorApplySuccess": "申请成功。", + "ErrorBagFull": "背包已满。", + "ErrorBreakthroughSuccess": "突破成功。", + "ErrorBuyFailed": "购买失败。", + "ErrorBuySuccess": "购买成功。", + "ErrorChallengeTimesNotEnough": "今日挑战次数不足。", + "ErrorCollectionUnlock": "请在关卡中拾取藏品解锁。", + "ErrorConditionNotMet": "条件不足。", + "ErrorCopySuccess": "复制成功。", + "ErrorCreateGuildSuccess": "创建公会成功。", + "ErrorCurrencyNotEnough": "货币不足。", + "ErrorDiamondNotEnough": "星棱幻晶不足。", + "ErrorEnergyNotEnough": "体力不足。", + "ErrorEquipBreakthroughSuccess": "装备升品成功。", + "ErrorEquipStrengthSuccess": "装备强化成功。", + "ErrorFriendAddOrNotExist": "玩家已添加或不存在。", + "ErrorFriendApplyRefuse": "已拒绝成为好友。", + "ErrorFriendApplySuccess": "已同意成为好友。", + "ErrorFusionRuneCountMax": "合成铭文所需数量已达上限。", + "ErrorFusionRuneCountNotEnough": "合成铭文所需数量不足。", + "ErrorFusionRuneNotFusion": "该铭文不能合成。", + "ErrorFusionRuneRarityNotSame": "合成铭文必须是相同稀有度。", + "ErrorGuildNameEmpty": "公会名称不能为空。", + "ErrorGuildNameTooLong": "公会名称不能超过6个字符。", + "ErrorGuildSetSuccess": "设置公会成功。", + "ErrorHeroLevelUnlock": "修士{0}级解锁。", + "ErrorHeroNotUnlock": "该伙伴尚未获得,需前往旅人驿站招募哦。", + "ErrorHeroStarUpSuccess": "升星成功。", + "ErrorInvalidGuideId": "无效的引导。", + "ErrorItemDailyUseTimesReached": "该道具今日使用次数已达上限。", + "ErrorItemNotEnough": "道具不足。", + "ErrorJoinSuccess": "加入成功。", + "ErrorLearnSuccess": "学习成功。", + "ErrorLevelNotEnough": "等级不足。", + "ErrorLevelNotExist": "关卡不存在。", + "ErrorMaterialNotEnough": "材料不足。", + "ErrorNameNotAvailable": "含有敏感字符,无法使用该名字。", + "ErrorNameOnlyChinese": "昵称只能包含中文。", + "ErrorNoFreePos": "人数已达上限。", + "ErrorNoHeroCarry": "无上阵伙伴。", + "ErrorNoHeroInTeam": "至少上阵一名修士。", + "ErrorNoPackagePurchased": "未购买礼包。", + "ErrorNoReward": "暂无奖励。", + "ErrorNotMeetClaimCondition": "暂未达到领取条件。", + "ErrorNotNewApply": "暂无新的好友申请。", + "ErrorNotPassBigBox": "请通关当前所有关卡后领取。", + "ErrorNotPassBigBoxStar": "满星通过前置关卡后解锁。", + "ErrorNotSettlementTime": "未到结算时间。", + "ErrorNotUnlock": "未解锁。", + "ErrorPassNotOpen": "关卡未开放。", + "ErrorPassNotUnlock": "通关后解锁。", + "ErrorPleaseInputName": "请输入昵称。", + "ErrorPreyFailed": "祈愿失败。", + "ErrorPushBoxLock": "通过{0}后解锁。", + "ErrorQuickSweepTimesNotEnough": "快速游历次数不足。", + "ErrorQuitGuildSuccess": "退出公会成功。", + "ErrorRefreshTimesNotEnough": "刷新次数不足。", + "ErrorRetreatNotNow": "现在还不能撤离。", + "ErrorRewardAlreadyClaimed": "您已领奖。", + "ErrorRunePutOnSuccess": "镶嵌成功。", + "ErrorRuneSlotFull": "铭文槽已满,请先卸下。", + "ErrorSaveHostage": "解救{0}个人质后解锁。", + "ErrorSaveSuccess": "保存成功。", + "ErrorScoreNotEnough": "积分不足。", + "ErrorSendGiftSuccess": "赠送成功", + "ErrorSetSuccess": "修改成功。", + "ErrorSomethingNotEnough": "{0}不足。", + "ErrorTakesuccess": "领取成功。", + "ErrorTargetNotEnough": "{0}不足。", + "ErrorTaskFailed": "领取失败。", + "ErrorTaskNotFinish": "任务未完成。", + "ErrorTaskRewardNotClaimed": "任务奖励未领取。", + "ErrorTimesNotEnough": "次数不足。", + "ErrorTitleNotUnlock": "称号未解锁。", + "ErrorUpLevelFirst": "请先升级。", + "ErrorUpLevelSuccess": "升级成功。", + "ErrorUpStage1First": "请先升品。", + "ErrorUpStageFirst": "请先升阶。", + "ErrorUpStageSuccess": "升阶成功。", + "ErrorUpStrengthFirst": "请先强化。", + "ErrorVipLevelNotEnough": "权益等级不足。", + "TipChallengeTimesConfirm": "\u3000是否消耗{0}进行挑战。\n\u3000今日剩余({1}/{2})次。", + "TipFusionSuccess": "合成成功。" + }, + "duplicates_removed": [ + {"kept": "ErrorTargetNotEnough", "removed": "ErrorSomethingNotEnough", "text": "{0}不足。"}, + {"kept": "ErrorBuyFailed", "removed": "ErrorSaveFailed", "text": "保存失败。"} + ], + "unreferenced_constants": [ + "ErrorUseFailed", "ErrorStrengthLimit", "ErrorRecruitTimesNotEnough", + "ErrorRecruitTicketNotEnough", "ErrorRecruitDiamondNotEnough", + "ErrorUpStarFirst", "ErrorUpStrengthFirst", "ErrorTujianProgressNotEnough", + "ErrorHeroMaxCountReached", "ErrorNoHeroToCombine", "ErrorStrengthSuccess", + "ErrorSaveFailed", "ErrorInvalidId", "ErrorReceiveConditionNotMet" + ] +} diff --git a/wenzijiaoben/py/__pycache__/generate_doc.cpython-311.pyc b/wenzijiaoben/py/__pycache__/generate_doc.cpython-311.pyc new file mode 100644 index 0000000..b73db0b Binary files /dev/null and b/wenzijiaoben/py/__pycache__/generate_doc.cpython-311.pyc differ diff --git a/wenzijiaoben/py/generate_doc.py b/wenzijiaoben/py/generate_doc.py new file mode 100644 index 0000000..960fbfe --- /dev/null +++ b/wenzijiaoben/py/generate_doc.py @@ -0,0 +1,568 @@ +# -*- coding: utf-8 -*- +""" +生成《不朽封仙》游戏文字脚本文档 +""" + +import csv +import os +import glob +from docx import Document +from docx.shared import Pt, Cm, Inches +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.enum.table import WD_TABLE_ALIGNMENT +from docx.oxml.ns import qn + +# ============ 路径配置 ============ +CSV_DIR = r"d:\p-L12\work\cvs" +GAME_DIR = r"d:\p-L12\P-L12_game\unity\Assets\Resources\Resources_moved\gui\atlas_ui" +ICON_DIRS = { + "product": os.path.join(GAME_DIR, "atlas_product_icons"), + "skill": os.path.join(GAME_DIR, "atlas_skill"), + "rune": os.path.join(GAME_DIR, "atlas_rune_icons"), + "talent": os.path.join(GAME_DIR, "atlas_talent_icons"), + "equip": os.path.join(GAME_DIR, "atlas_equip_icons"), +} +OUTPUT_PATH = r"d:\p-L12\work\《不朽封仙》游戏文字脚本.docx" + +# ============ 辅助函数 ============ + +def read_csv(filename): + """读取CSV文件,跳过前3行(中文头、类型头、英文头),返回数据行""" + path = os.path.join(CSV_DIR, filename) + # 尝试多种编码 + for encoding in ["utf-8-sig", "gbk", "gb2312", "utf-8"]: + try: + with open(path, "r", encoding=encoding) as f: + reader = csv.reader(f) + rows = list(reader) + return rows[3:] # skip header rows + except (UnicodeDecodeError, UnicodeError): + continue + raise ValueError(f"无法读取文件 {filename},尝试了所有编码") + +def find_image(icon_name, icon_dir_key): + """根据icon名称查找图片文件路径,返回完整路径或None""" + icon_dir = ICON_DIRS.get(icon_dir_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) + import re + 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 + +def add_image_to_cell(cell, image_path, height_cm=3): + """在表格单元格中插入图片,锁定纵横比,设置高度""" + paragraph = cell.paragraphs[0] + paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER + run = paragraph.add_run() + run.add_picture(image_path, height=Cm(height_cm)) + +def set_cell_text(cell, text): + """设置单元格文本""" + cell.text = "" + paragraph = cell.paragraphs[0] + paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER + run = paragraph.add_run(str(text)) + run.font.size = Pt(10) + +def add_table_with_icons(doc, headers, rows_data, icon_dir_key): + """添加带图标的表格""" + table = doc.add_table(rows=1, cols=len(headers), style="Table Grid") + table.alignment = WD_TABLE_ALIGNMENT.CENTER + # 设置表头 + for i, header in enumerate(headers): + cell = table.rows[0].cells[i] + cell.text = header + for paragraph in cell.paragraphs: + paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER + for run in paragraph.runs: + run.font.bold = True + run.font.size = Pt(10) + # 添加数据行 + for row_data in rows_data: + row = table.add_row() + name, icon_name, desc = row_data[0], row_data[1], row_data[2] if len(row_data) > 2 else "" + set_cell_text(row.cells[0], name) + # 图标列 + img_path = find_image(icon_name, icon_dir_key) if icon_name else None + if img_path: + add_image_to_cell(row.cells[1], img_path) + else: + set_cell_text(row.cells[1], "") + # 描述列 + if desc: + set_cell_text(row.cells[2], desc) + +def add_table_equip(doc, headers, rows_data, icon_dir_key): + """添加装备表格(只有名称和图标两列)""" + table = doc.add_table(rows=1, cols=len(headers), style="Table Grid") + table.alignment = WD_TABLE_ALIGNMENT.CENTER + for i, header in enumerate(headers): + cell = table.rows[0].cells[i] + cell.text = header + for paragraph in cell.paragraphs: + paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER + for run in paragraph.runs: + run.font.bold = True + run.font.size = Pt(10) + for row_data in rows_data: + row = table.add_row() + name, icon_name = row_data[0], row_data[1] + set_cell_text(row.cells[0], name) + img_path = find_image(icon_name, icon_dir_key) if icon_name else None + if img_path: + add_image_to_cell(row.cells[1], img_path) + else: + set_cell_text(row.cells[1], "") + + +# ============ 数据准备 ============ + +def get_system_prompts(): + """获取被引用的GameTextConst系统提示""" + # 所有GameTextConst常量及其文本值 + all_consts = { + "ErrorTaskFailed": "领取失败。", + "ErrorBuyFailed": "购买失败。", + "ErrorUseFailed": "使用失败。", + "ErrorTakesuccess": "领取成功。", + "ErrorStrengthLimit": "强化已达上限。", + "ErrorChallengeTimesNotEnough": "今日挑战次数不足。", + "ErrorRecruitTimesNotEnough": "今日可招募次数不足。", + "ErrorRecruitTicketNotEnough": "召唤道具与月灵玉璧不足。", + "ErrorRecruitDiamondNotEnough": "星棱幻晶不足,无法进行招募。", + "ErrorUpStageFirst": "请先升阶。", + "ErrorUpStage1First": "请先升品。", + "ErrorUpLevelFirst": "请先升级。", + "ErrorUpStarFirst": "请先升星。", + "ErrorUpStrengthFirst": "请先强化。", + "ErrorLevelNotExist": "关卡不存在。", + "ErrorInvalidGuideId": "无效的引导。", + "ErrorItemNotEnough": "道具不足。", + "ErrorRewardAlreadyClaimed": "您已领奖。", + "ErrorNotSettlementTime": "未到结算时间。", + "ErrorNoReward": "暂无奖励。", + "ErrorHeroStarUpSuccess": "升星成功。", + "ErrorTujianProgressNotEnough": "图鉴进度未达到领取条件。", + "ErrorUpLevelSuccess": "升级成功。", + "ErrorUpStageSuccess": "升阶成功。", + "ErrorGuildSetSuccess": "设置公会成功。", + "ErrorNoPackagePurchased": "未购买礼包。", + "ErrorNotMeetClaimCondition": "暂未达到领取条件。", + "ErrorNameNotAvailable": "含有敏感字符,无法使用该名字。", + "ErrorNameOnlyChinese": "昵称只能包含中文。", + "ErrorPleaseInputName": "请输入昵称。", + "ErrorSetSuccess": "修改成功。", + "ErrorCurrencyNotEnough": "货币不足。", + "ErrorHeroMaxCountReached": "伙伴达到最大人数。", + "ErrorNoHeroToCombine": "没有可合成的伙伴。", + "ErrorJoinSuccess": "加入成功。", + "ErrorApplySuccess": "申请成功。", + "ErrorEnergyNotEnough": "体力不足。", + "ErrorBuySuccess": "购买成功。", + "ErrorQuitGuildSuccess": "退出公会成功。", + "ErrorGuildNameEmpty": "公会名称不能为空。", + "ErrorGuildNameTooLong": "公会名称不能超过6个字符。", + "ErrorCreateGuildSuccess": "创建公会成功。", + "ErrorMaterialNotEnough": "材料不足。", + "ErrorEquipStrengthSuccess": "装备强化成功。", + "ErrorEquipBreakthroughSuccess": "装备升品成功。", + "ErrorStrengthSuccess": "强化成功。", + "ErrorDiamondNotEnough": "星棱幻晶不足。", + "ErrorQuickSweepTimesNotEnough": "快速游历次数不足。", + "ErrorCopySuccess": "复制成功。", + "ErrorReceiveConditionNotMet": "未达成领取条件。", + "ErrorAlreadyClaimed": "已领取。", + "ErrorItemDailyUseTimesReached": "该道具今日使用次数已达上限。", + "ErrorConditionNotMet": "条件不足。", + "ErrorVipLevelNotEnough": "权益等级不足。", + "ErrorLevelNotEnough": "等级不足。", + "ErrorScoreNotEnough": "积分不足。", + "ErrorPassNotOpen": "关卡未开放。", + "ErrorPreyFailed": "祈愿失败。", + "ErrorInvalidId": "无效的ID。", + "ErrorSaveSuccess": "保存成功。", + "ErrorSaveFailed": "保存失败。", + "ErrorNoHeroCarry": "无上阵伙伴。", + "ErrorNoHeroInTeam": "至少上阵一名修士。", + "ErrorNoFreePos": "人数已达上限。", + "ErrorRefreshTimesNotEnough": "刷新次数不足。", + "ErrorTimesNotEnough": "次数不足。", + "ErrorHeroLevelUnlock": "修士{0}级解锁。", + "TipChallengeTimesConfirm": "\u3000是否消耗{0}进行挑战。\n\u3000今日剩余({1}/{2})次。", + "ErrorFusionRuneCountNotEnough": "合成铭文所需数量不足。", + "TipFusionSuccess": "合成成功。", + "ErrorFusionRuneRarityNotSame": "合成铭文必须是相同稀有度。", + "ErrorFusionRuneCountMax": "合成铭文所需数量已达上限。", + "ErrorFusionRuneNotFusion": "该铭文不能合成。", + "ErrorRunePutOnSuccess": "镶嵌成功。", + "ErrorTitleNotUnlock": "称号未解锁。", + "ErrorHeroNotUnlock": "该伙伴尚未获得,需前往旅人驿站招募哦。", + "ErrorRuneSlotFull": "铭文槽已满,请先卸下。", + "ErrorPushBoxLock": "通过{0}后解锁。", + "ErrorFriendApplySuccess": "已同意成为好友。", + "ErrorFriendApplyRefuse": "已拒绝成为好友。", + "ErrorFriendAddOrNotExist": "玩家已添加或不存在。", + "ErrorBagFull": "背包已满。", + "ErrorTargetNotEnough": "{0}不足。", + "ErrorPassNotUnlock": "通关后解锁。", + "ErrorSaveHostage": "解救{0}个人质后解锁。", + "ErrorSomethingNotEnough": "{0}不足。", + "ErrorRetreatNotNow": "现在还不能撤离。", + "ErrorNotUnlock": "未解锁。", + "ErrorSendGiftSuccess": "赠送成功", + "ErrorCollectionUnlock": "请在关卡中拾取藏品解锁。", + "ErrorTaskNotFinish": "任务未完成。", + "ErrorTaskRewardNotClaimed": "任务奖励未领取。", + "ErrorNotNewApply": "暂无新的好友申请。", + "ErrorNotPassBigBox": "请通关当前所有关卡后领取。", + "ErrorNotPassBigBoxStar": "满星通过前置关卡后解锁。", + "ErrorBreakthroughSuccess": "突破成功。", + "ErrorLearnSuccess": "学习成功。", + } + # 被引用的常量(排除只在注释中引用的) + referenced = { + "ErrorAlreadyClaimed", "ErrorApplySuccess", "ErrorBagFull", + "ErrorBreakthroughSuccess", "ErrorBuyFailed", "ErrorBuySuccess", + "ErrorChallengeTimesNotEnough", "ErrorCollectionUnlock", + "ErrorConditionNotMet", "ErrorCopySuccess", "ErrorCreateGuildSuccess", + "ErrorCurrencyNotEnough", "ErrorDiamondNotEnough", "ErrorEnergyNotEnough", + "ErrorEquipBreakthroughSuccess", "ErrorEquipStrengthSuccess", + "ErrorFriendAddOrNotExist", "ErrorFriendApplyRefuse", + "ErrorFriendApplySuccess", "ErrorFusionRuneCountMax", + "ErrorFusionRuneCountNotEnough", "ErrorFusionRuneNotFusion", + "ErrorFusionRuneRarityNotSame", "ErrorGuildNameEmpty", + "ErrorGuildNameTooLong", "ErrorGuildSetSuccess", "ErrorHeroLevelUnlock", + "ErrorHeroNotUnlock", "ErrorHeroStarUpSuccess", "ErrorInvalidGuideId", + "ErrorItemDailyUseTimesReached", "ErrorItemNotEnough", + "ErrorJoinSuccess", "ErrorLearnSuccess", "ErrorLevelNotEnough", + "ErrorLevelNotExist", "ErrorMaterialNotEnough", "ErrorNameNotAvailable", + "ErrorNameOnlyChinese", "ErrorNoFreePos", "ErrorNoHeroCarry", + "ErrorNoHeroInTeam", "ErrorNoPackagePurchased", "ErrorNoReward", + "ErrorNotMeetClaimCondition", "ErrorNotNewApply", "ErrorNotPassBigBox", + "ErrorNotPassBigBoxStar", "ErrorNotSettlementTime", "ErrorNotUnlock", + "ErrorPassNotOpen", "ErrorPassNotUnlock", "ErrorPleaseInputName", + "ErrorPreyFailed", "ErrorPushBoxLock", "ErrorQuickSweepTimesNotEnough", + "ErrorQuitGuildSuccess", "ErrorRefreshTimesNotEnough", + "ErrorRetreatNotNow", "ErrorRewardAlreadyClaimed", + "ErrorRunePutOnSuccess", "ErrorRuneSlotFull", "ErrorSaveHostage", + "ErrorSaveSuccess", "ErrorScoreNotEnough", "ErrorSendGiftSuccess", + "ErrorSetSuccess", "ErrorSomethingNotEnough", "ErrorTakesuccess", + "ErrorTargetNotEnough", "ErrorTaskFailed", "ErrorTaskNotFinish", + "ErrorTaskRewardNotClaimed", "ErrorTimesNotEnough", + "ErrorTitleNotUnlock", "ErrorUpLevelFirst", "ErrorUpLevelSuccess", + "ErrorUpStage1First", "ErrorUpStageFirst", "ErrorUpStageSuccess", + "ErrorUpStrengthFirst", "ErrorVipLevelNotEnough", + "TipChallengeTimesConfirm", "TipFusionSuccess", + } + # 只保留被引用的,去重文本值 + seen_texts = set() + result = [] + for name, text in all_consts.items(): + if name in referenced and text not in seen_texts: + seen_texts.add(text) + result.append(text) + return result + +def get_quest_texts(): + """获取任务文本(category 1和2的desc字段,去重)""" + rows = read_csv("quest.csv") + seen = set() + result = [] + for row in rows: + if len(row) > 3: + category = row[1].strip() + desc = row[3].strip() + if category in ("1", "2") and desc and desc not in seen: + seen.add(desc) + result.append(desc) + return result + +def get_achievement_texts(): + """获取成就文本(category 9的desc字段,去重)""" + rows = read_csv("quest.csv") + seen = set() + result = [] + for row in rows: + if len(row) > 3: + category = row[1].strip() + desc = row[3].strip() + if category == "9" and desc and desc not in seen: + seen.add(desc) + result.append(desc) + return result + +def get_guide_texts(): + """获取新手指引文本(guide.csv的desc字段,去重)""" + rows = read_csv("guide.csv") + seen = set() + result = [] + for row in rows: + if len(row) > 7: + desc = row[7].strip() + if desc and desc not in seen: + seen.add(desc) + result.append(desc) + return result + +def get_prop_data(): + """获取道具数据 [(name, icon, tips), ...]""" + rows = read_csv("prop.csv") + result = [] + for row in rows: + if len(row) > 7: + name = row[2].strip() + tips = row[6].strip() + icon = row[7].strip() + if name: + result.append((name, icon, tips)) + return result + +def get_skill_data(): + """获取技能数据 [(name, icon, desc), ...]""" + rows = read_csv("skill.csv") + result = [] + for row in rows: + if len(row) > 21: + name = row[1].strip() + icon = row[6].strip() + desc = row[21].strip() + if name: + result.append((name, icon, desc)) + return result + +def get_rune_data(): + """获取灵石数据 [(name, icon, desc), ...]""" + rows = read_csv("rune.csv") + result = [] + for row in rows: + if len(row) > 8: + name = row[1].strip() + icon = row[2].strip() + desc = row[8].strip() + if name: + result.append((name, icon, desc)) + return result + +def get_talent_data(): + """获取天赋数据 [(name, icon, desc), ...] 去重""" + rows = read_csv("playerlevel.csv") + seen = set() + result = [] + for row in rows: + if len(row) > 15: + name = row[13].strip() + desc = row[14].strip() + icon = row[15].strip() + if name and name not in seen: + seen.add(name) + result.append((name, icon, desc)) + return result + +def get_equip_data(): + """获取装备数据 [(name, icon), ...] 去重""" + rows = read_csv("equip.csv") + seen = set() + result = [] + for row in rows: + if len(row) > 5: + name = row[1].strip() + icon = row[5].strip() + if name and name not in seen: + seen.add(name) + result.append((name, icon)) + return result + +def get_level_names(): + """获取关卡名称(fight_sample.csv的name字段,去重)""" + rows = read_csv("fight_sample.csv") + seen = set() + result = [] + for row in rows: + if len(row) > 2: + name = row[2].strip() + if name and name not in seen: + seen.add(name) + result.append(name) + return result + +# 玩家随机名称文本(保持不变) +PLAYER_NAMES = [ + ("李", "子轩"), ("王", "梓涵"), ("张", "浩然"), ("刘", "欣怡"), + ("陈", "宇轩"), ("杨", "晨曦"), ("赵", "俊杰"), ("黄", "雅静"), + ("周", "天佑"), ("吴", "梦琪"), ("徐", "博文"), ("孙", "思彤"), + ("胡", "睿智"), ("朱", "静怡"), ("高", "昊天"), ("林", "雨桐"), + ("何", "泽宇"), ("郭", "婉清"), ("马", "俊熙"), ("罗", "雪柔"), + ("梁", "子豪"), ("宋", "依琳"), ("郑", "宇航"), ("谢", "诗涵"), + ("韩", "志强"), ("唐", "梦瑶"), ("冯", "文博"), ("于", "雅楠"), + ("董", "天宇"), ("萧", "欣妍"), ("程", "子睿"), ("曹", "静雯"), + ("袁", "俊豪"), ("邓", "雨欣"), ("许", "浩宇"), ("傅", "语嫣"), + ("沈", "子涵"), ("曾", "思雨"), ("彭", "博涛"), ("吕", "雅婷"), + ("苏", "天翔"), ("卢", "梦洁"), ("蒋", "文轩"), ("蔡", "雪梅"), + ("贾", "志远"), ("丁", "依娜"), ("魏", "子安"), ("薛", "静雅"), + ("叶", "俊峰"), ("阎", "雨婷"), ("余", "浩轩"), ("潘", "诗琪"), + ("杜", "子恒"), ("戴", "思雅"), ("夏", "博超"), ("钟", "雅琪"), + ("汪", "天瑞"), ("田", "梦菲"), ("任", "文昊"), ("姜", "雪莲"), + ("范", "志鹏"), ("方", "依婷"), ("石", "子健"), ("姚", "静淑"), + ("谭", "俊凯"), ("廖", "雨菲"), ("邹", "浩南"), ("熊", "诗瑶"), + ("金", "子宁"), ("陆", "思琪"), ("郝", "博远"), ("孔", "雅静"), + ("白", "天浩"), ("崔", "梦婷"), ("康", "文杰"), ("毛", "雪琪"), + ("邱", "志伟"), ("秦", "依静"), ("江", "子文"), ("史", "静怡"), + ("顾", "俊杰"), ("侯", "雨萱"), ("邵", "浩天"), ("孟", "诗涵"), + ("龙", "子辰"), ("万", "思妍"), ("段", "博文"), ("雷", "雅欣"), + ("钱", "天佑"), ("汤", "梦瑶"), ("尹", "文豪"), ("黎", "雪莹"), + ("易", "志强"), ("常", "依琳"), ("武", "子轩"), ("乔", "静雯"), + ("贺", "俊熙"), ("赖", "雨桐"), ("龚", "浩宇"), ("文", "诗琪"), +] + + +# ============ 文档生成 ============ + +# 中文编号 +CN_NUMBERS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一"] + +def add_title(doc, text): + """添加文档标题:宋体、二号(22pt)、加粗、居中、无下划线""" + para = doc.add_paragraph() + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + run = para.add_run(text) + run.font.name = "宋体" + run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体") + run.font.size = Pt(22) + run.font.bold = True + run.font.underline = False + run.font.color.rgb = None # 确保黑色,无蓝色 + +def add_section_heading(doc, cn_num, title_text): + """添加小标题:仿宋、四号(14pt)、加粗、中文编号""" + para = doc.add_paragraph() + run = para.add_run(f"{cn_num}、{title_text}") + run.font.name = "仿宋" + run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") + run.font.size = Pt(14) + run.font.bold = True + +def add_numbered_paragraph(doc, index, text): + """添加编号段落:仿宋、四号(14pt)、不加粗、阿拉伯数字编号""" + para = doc.add_paragraph() + run = para.add_run(f"{index}.{text}") + run.font.name = "仿宋" + run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") + run.font.size = Pt(14) + run.font.bold = False + +def add_numbered_items(doc, items): + """批量添加编号段落""" + for i, text in enumerate(items, 1): + add_numbered_paragraph(doc, i, text) + +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, header in enumerate(["姓", "名"]): + cell = table.rows[0].cells[i] + cell.text = "" + para = cell.paragraphs[0] + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + run = para.add_run(header) + run.font.name = "仿宋" + run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") + run.font.size = Pt(14) + run.font.bold = True + # 数据行 + for surname, given_name in names: + row = table.add_row() + for j, text in enumerate([surname, given_name]): + cell = row.cells[j] + cell.text = "" + para = cell.paragraphs[0] + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + run = para.add_run(text) + run.font.name = "仿宋" + run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") + run.font.size = Pt(14) + run.font.bold = False + +def build_document(): + doc = Document() + + # 设置默认字体 + style = doc.styles["Normal"] + style.font.name = "仿宋" + style.font.size = Pt(14) + style._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") + + # 标题:宋体、二号、加粗、无蓝色下划线 + add_title(doc, "《不朽封仙》文字脚本") + + # ======= 一、系统提示 ======= + add_section_heading(doc, CN_NUMBERS[0], "系统提示") + add_numbered_items(doc, get_system_prompts()) + + # ======= 二、任务文本 ======= + add_section_heading(doc, CN_NUMBERS[1], "任务文本") + add_numbered_items(doc, get_quest_texts()) + + # ======= 三、成就文本 ======= + add_section_heading(doc, CN_NUMBERS[2], "成就文本") + add_numbered_items(doc, get_achievement_texts()) + + # ======= 四、玩家随机名称文本 ======= + add_section_heading(doc, CN_NUMBERS[3], "玩家随机名称文本") + add_name_table(doc, PLAYER_NAMES) + + # ======= 五、新手指引文本 ======= + add_section_heading(doc, CN_NUMBERS[4], "新手指引文本") + add_numbered_items(doc, get_guide_texts()) + + # ======= 六、道具说明 ======= + add_section_heading(doc, CN_NUMBERS[5], "道具说明") + prop_data = get_prop_data() + add_table_with_icons(doc, ["道具名称", "图标", "道具描述"], prop_data, "product") + + # ======= 七、技能 ======= + add_section_heading(doc, CN_NUMBERS[6], "技能") + skill_data = get_skill_data() + add_table_with_icons(doc, ["技能名称", "技能图标", "技能描述"], skill_data, "skill") + + # ======= 八、灵石 ======= + add_section_heading(doc, CN_NUMBERS[7], "灵石") + rune_data = get_rune_data() + add_table_with_icons(doc, ["灵石名称", "灵石图标", "灵石介绍"], rune_data, "rune") + + # ======= 九、天赋 ======= + add_section_heading(doc, CN_NUMBERS[8], "天赋") + talent_data = get_talent_data() + add_table_with_icons(doc, ["天赋名称", "天赋图标", "天赋介绍"], talent_data, "talent") + + # ======= 十、装备 ======= + add_section_heading(doc, CN_NUMBERS[9], "装备") + equip_data = get_equip_data() + add_table_equip(doc, ["装备名称", "装备图标"], equip_data, "equip") + + # ======= 十一、关卡名称 ======= + add_section_heading(doc, CN_NUMBERS[10], "关卡名称") + add_numbered_items(doc, get_level_names()) + + # 保存文档 + doc.save(OUTPUT_PATH) + print(f"文档已生成: {OUTPUT_PATH}") + + +if __name__ == "__main__": + build_document() diff --git a/wenzijiaoben/py/universal_tool.py b/wenzijiaoben/py/universal_tool.py new file mode 100644 index 0000000..94d9609 --- /dev/null +++ b/wenzijiaoben/py/universal_tool.py @@ -0,0 +1,682 @@ +# -*- coding: utf-8 -*- +""" +通用游戏文字脚本文档生成工具 + +功能:自动扫描项目目录,识别标准结构,生成游戏文字脚本Word文档。 +无需手动配置路径,只需提供项目根目录即可自动完成全部流程。 + +标准目录结构约定: + / + ├── *_config/ ← 配置表目录 (xlsx/csv) + ├── *_game/ ← 游戏客户端目录 + │ └── unity/Assets/Resources/.../atlas_ui/ ← 图标资源 + │ └── unity/Assets/Scripts/.../*Const*.cs ← 系统提示常量 + └── work/ ← 工作目录 (CSV缓存、输出等) + +用法: + python universal_tool.py [项目路径] [--output 输出路径] + +依赖:pip install openpyxl python-docx +""" + +import csv +import os +import re +import sys +import glob +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 + +# ============ 常量 ============ +CN_NUMBERS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一"] + +# 11章节数据源定义(字段名取自CSV英文头行第3行) +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目录: work/cvs 优先,其次 config_dir 本身 + 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 中查找文件(支持 .csv/.xlsx)""" + for d in [self.csv_dir, self.config_dir]: + if not d: + continue + for ext in [".csv", ".xlsx"]: + 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" CSV目录: {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) + + +# ================================================================ +# 二、CSV 数据读取(自动编码 + 动态字段名解析) +# ================================================================ + +def read_csv(filepath): + """ + 读取CSV文件,自动检测编码 (utf-8-sig/gbk/gb2312/utf-8)。 + 返回 (headers, rows): + headers: 第3行英文字段名列表 + rows: 第4行起的数据行列表 (list of list) + """ + 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 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 xlsx_to_csv_batch(config_dir, output_dir): + """将 config_dir 中所有 xlsx 批量转为 csv""" + from openpyxl import load_workbook + os.makedirs(output_dir, exist_ok=True) + count = 0 + for xlsx in glob.glob(os.path.join(config_dir, "*.xlsx")): + fname = os.path.basename(xlsx) + if fname.startswith("~$"): + continue + csv_path = os.path.join(output_dir, os.path.splitext(fname)[0] + ".csv") + try: + wb = load_workbook(xlsx, read_only=True, data_only=True) + ws = wb.active + with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f) + for row in ws.iter_rows(values_only=True): + writer.writerow([c if c is not None else "" for c in row]) + wb.close() + count += 1 + except Exception: + pass + # 复制非xlsx文件 + import shutil + for entry in os.listdir(config_dir): + src = os.path.join(config_dir, entry) + if not os.path.isfile(src) or entry.endswith(".xlsx"): + continue + dst = os.path.join(output_dir, entry) + if not os.path.exists(dst): + shutil.copy2(src, dst) + return count + + +# ================================================================ +# 三、图片资源查找 +# ================================================================ + +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) + + +def add_numbered_items(doc, items): + """编号列表:仿宋 四号 不加粗""" + for i, text in enumerate(items, 1): + p = doc.add_paragraph() + _run(p, f"{i}.{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.csv,否则使用默认列表)""" + fp = scanner.find_file("name") + if fp and fp.endswith(".csv"): + headers, rows = read_csv(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): + """通用:从CSV中按字段名提取去重文本列表""" + fp = scanner.find_file(csv_name) + if not fp or not fp.endswith(".csv"): + return [] + headers, rows = read_csv(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): + """通用:从CSV中提取多字段表格数据 [(f1, f2, f3, ...), ...]""" + fp = scanner.find_file(csv_name) + if not fp or not fp.endswith(".csv"): + return [] + headers, rows = read_csv(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] 准备数据源...") + + # 确保CSV可用 + if not scanner.csv_dir or not any(f.endswith(".csv") for f in os.listdir(scanner.csv_dir)): + if scanner.config_dir: + print(f" CSV目录为空,正在从 {os.path.basename(scanner.config_dir)} 转换xlsx...") + cvs_out = os.path.join(scanner.work_dir or scanner.root, "cvs") + n = xlsx_to_csv_batch(scanner.config_dir, cvs_out) + scanner.csv_dir = cvs_out + print(f" 已转换 {n} 个xlsx文件") + + # 预加载数据 + 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() diff --git a/wenzijiaoben/py/xlsx_to_csv.py b/wenzijiaoben/py/xlsx_to_csv.py new file mode 100644 index 0000000..51b2083 --- /dev/null +++ b/wenzijiaoben/py/xlsx_to_csv.py @@ -0,0 +1,104 @@ +""" +xlsx转csv工具 +- 将指定目录下所有.xlsx文件转换为.csv格式 +- 输出路径:D:\15CVS +- 只处理每个xlsx文件的第一个工作表(主数据表) +- 忽略以~$开头的临时文件 +- CSV编码:UTF-8 with BOM +- 避免覆盖同名文件(如已存在则跳过并提示) +""" + +import os +import sys +import csv +import glob +from openpyxl import load_workbook + + +def xlsx_to_csv(src_dir, out_dir): + """将源目录下所有xlsx文件转换为csv格式""" + # 确保输出目录存在 + os.makedirs(out_dir, exist_ok=True) + + # 统计计数 + success_count = 0 + skip_count = 0 + fail_count = 0 + + # 遍历源目录下所有xlsx文件(不递归子目录) + xlsx_files = glob.glob(os.path.join(src_dir, "*.xlsx")) + + for xlsx_path in xlsx_files: + filename = os.path.basename(xlsx_path) + + # 跳过以~$开头的临时文件 + if filename.startswith("~$"): + print(f"[跳过] 临时文件: {filename}") + skip_count += 1 + continue + + csv_filename = os.path.splitext(filename)[0] + ".csv" + csv_path = os.path.join(out_dir, csv_filename) + + # 检查目标文件是否已存在 + if os.path.exists(csv_path): + print(f"[跳过] 文件已存在: {csv_filename}") + skip_count += 1 + continue + + try: + print(f"[转换] {filename} -> {csv_filename}", end=" ... ") + + # 读取xlsx文件(只读模式,提高性能) + wb = load_workbook(xlsx_path, read_only=True, data_only=True) + ws = wb.active # 获取第一个工作表 + + # 写入CSV文件 + with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f) + + for row in ws.iter_rows(values_only=True): + # 将None转为空字符串,其他值保持原样 + csv_row = [cell if cell is not None else "" for cell in row] + writer.writerow(csv_row) + + wb.close() + print("成功") + success_count += 1 + + except Exception as e: + print(f"失败") + print(f" [错误] {filename}: {e}") + fail_count += 1 + # 清理可能生成的不完整文件 + if os.path.exists(csv_path): + try: + os.remove(csv_path) + except Exception: + pass + + # 打印统计结果 + print("\n" + "=" * 50) + print(f"转换完成!") + print(f" 成功: {success_count} 个") + print(f" 跳过: {skip_count} 个") + print(f" 失败: {fail_count} 个") + print(f" 总计: {success_count + skip_count + fail_count} 个") + print("=" * 50) + + +if __name__ == "__main__": + src_dir = r"d:\A工作\P-L15_config" + out_dir = r"D:\15CVS" + + # 支持命令行参数修改源目录 + if len(sys.argv) > 1: + src_dir = sys.argv[1] + if len(sys.argv) > 2: + out_dir = sys.argv[2] + + print(f"源目录: {src_dir}") + print(f"输出目录: {out_dir}") + print("-" * 50) + + xlsx_to_csv(src_dir, out_dir) diff --git a/wenzijiaoben/使用请看.txt b/wenzijiaoben/使用请看.txt new file mode 100644 index 0000000..67337cc --- /dev/null +++ b/wenzijiaoben/使用请看.txt @@ -0,0 +1 @@ +将PY放入项目文件中,运行universal_tool.py他会自动读取设定好的表格对应关系进行自动生成 \ No newline at end of file