修改配置的插件

This commit is contained in:
ShallowT1Dream
2026-06-02 14:11:28 +08:00
parent 9a0faaccd7
commit d4fbef751a
6 changed files with 1895 additions and 574 deletions

View File

@@ -0,0 +1,679 @@
import argparse
import json
import os
import re
import sys
from pathlib import Path
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
DEFAULT_CONFIG_DIR = PROJECT_ROOT / "Assets" / "Resources" / "Resources_moved" / "config"
DEFAULT_OUTPUT = Path(__file__).resolve().parent / "ConfigLinkData.json"
DEFAULT_TABLE_INFO = Path(__file__).resolve().parent / "table_info.json"
def parse_args():
parser = argparse.ArgumentParser(description="ConfigLinkViewer 配置表联动关系自动生成")
parser.add_argument("--config-dir", type=str, default=None,
help="JSON 配置导出目录 (默认: Assets/Resources/Resources_moved/config)")
parser.add_argument("--output", type=str, default=None,
help="输出 ConfigLinkData.json 路径")
parser.add_argument("--table-info", type=str, default=None,
help="表信息配置 JSON 路径 (默认: table_info.json)")
return parser.parse_args()
def load_table_info(filepath):
if filepath.exists():
with open(filepath, "r", encoding="utf-8") as f:
data = json.load(f)
entries = data.get("entries", [])
return {
entry["name"]: (entry.get("displayName", entry["name"]), entry.get("description", ""))
for entry in entries
}
return {}
TABLE_NAME_TO_CN = {
"hero": "英雄",
"enemy": "敌人",
"equip": "装备",
"prop": "道具",
"skill": "技能",
"buff": "增益效果",
"attribute": "属性",
"quest": "任务",
"player": "玩家",
"rune": "符文",
"recharge": "充值",
"checkpointreward": "关卡奖励",
"activity": "活动",
"map_act": "地图波次1",
"map_act2": "地图波次2",
"map_act3": "地图波次3",
"map_act4": "地图波次4",
"fight_sample": "主线关卡",
"fight_arena": "竞技场战斗",
"fight_fb1": "副本1",
"fight_fb2": "副本2",
"fight_fb3": "副本3",
"fight_fb4": "副本4",
"map": "地图",
}
def load_json_file(filepath):
try:
with open(filepath, "r", encoding="utf-8-sig") as f:
return json.load(f)
except UnicodeDecodeError:
with open(filepath, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError:
return []
def extract_fields_from_json(filepath):
data = load_json_file(filepath)
if isinstance(data, list) and len(data) > 0:
return list(data[0].keys())
elif isinstance(data, dict):
return list(data.keys())
return []
def detect_reward_format(values):
if not values:
return ""
sample = ""
for v in values[:5]:
if isinstance(v, str):
sample = v
break
elif isinstance(v, list) and len(v) > 0:
for item in v[:3]:
if isinstance(item, str):
sample = item
break
if sample:
break
if not sample:
return ""
if re.match(r"^item_\d+_\d+$", sample):
return "item_id_num"
if re.match(r"^hero_\d+$", sample):
return "hero_id"
if re.match(r"^rune_\d+_\d+$", sample):
return "item_id_num"
if re.match(r"^equip_\d+_\d+$", sample):
return "item_id_num"
if re.match(r"^Hero_\d+$", sample):
return "item_id_num"
if re.match(r"^\d+_\d+_\d+_\d+$", sample):
return "id_lv_count_delay"
if re.match(r"^\d+_\d+_\d+$", sample):
return "id_lv_num"
return ""
def detect_consumes_format(values):
if not values:
return ""
sample = ""
for v in values[:5]:
if isinstance(v, str):
sample = v
break
elif isinstance(v, list) and len(v) > 0:
for item in v[:3]:
if isinstance(item, str):
sample = item
break
if sample:
break
if not sample:
return ""
if re.match(r"^item_\d+_\d+$", sample):
return "item_id_num"
return ""
def detect_monster_format(values):
if not values:
return ""
sample = ""
for v in values[:5]:
if isinstance(v, str):
sample = v
break
elif isinstance(v, list) and len(v) > 0:
for item in v[:3]:
if isinstance(item, str):
sample = item
break
if sample:
break
if not sample:
return ""
if re.match(r"^\d+_\d+_\d+_\d+(\.\d+)?$", sample):
return "id_lv_count_delay"
if re.match(r"^\d+_\d+_\d+$", sample):
return "id_lv_num"
return ""
def get_relation_for_target_id(table_name):
mapping = {
"herolevel": ("hero", "关联英雄ID"),
"herostage": ("hero", "关联英雄ID"),
"herolevelinternal": ("enemy", "关联敌人ID"),
"enemylevel": ("enemy", "关联敌人ID"),
"equiplevel": ("equip", "关联装备ID"),
"equipstage": ("equip", "关联装备ID"),
"playerlevel": ("player", "关联玩家ID"),
"playerstage": ("player", "关联玩家ID"),
"runelevel": ("rune", "关联符文ID"),
"skilllevel": ("skill", "关联技能ID"),
"buff": ("skill", "关联技能ID"),
"map": ("map", "关联地图ID"),
"map1": ("map", "关联地图ID"),
"map2": ("map", "关联地图ID"),
"map3": ("map", "关联地图ID"),
"map_act": ("map", "关联地图ID"),
"map_act2": ("map", "关联地图ID"),
"map_act3": ("map", "关联地图ID"),
"map_act4": ("map", "关联地图ID"),
"test_map_act": ("map", "关联地图ID"),
"trainbreak": ("prop", "关联训练突破ID"),
"trainlevel": ("prop", "关联训练等级ID"),
}
if table_name in mapping:
return mapping[table_name]
return None
def build_relations(table_name, fields, sample_values):
relations = []
reward_fields = {
"rewards", "rewards2", "pass_rewards", "activateRewards", "reward",
"dailyQuestScoreRewards", "weeklyQuestScoreRewards", "achievementScoreRewards",
"heroTujianRewards",
}
consume_fields = {"consumes", "lostConsumes"}
monster_fields = {"monsters", "enemy_ids"}
equip_fields = {"equips"}
skill_fields = {"skill_ids", "skills"}
hero_fields = {"hero_ids"}
quest_fields = {"quest"}
attr_fields = {"attr_id", "level_attr_id", "stage_attr_id"}
shard_fields = {"shardItem"}
compose_fields = {"compose"}
next_fields = {"next"}
recharge_fields = {"recharge_id"}
product_fields = {"productId"}
out_enemy_fields = {"out_enemy_config"}
scene_fight_fields = {"fight_cf_name"}
scene_map_fields = {"map_cf_name"}
time_limit_fields = {"time_limit_boss"}
for field in fields:
if field == "target_id":
result = get_relation_for_target_id(table_name)
if result:
target, desc = result
relations.append({
"field": field,
"target": target,
"targetField": "id",
"format": "",
"description": desc,
})
continue
if field == "activityId":
relations.append({
"field": field,
"target": "activity",
"targetField": "id",
"format": "",
"description": "所属活动ID",
})
continue
if field == "reward_id":
relations.append({
"field": field,
"target": "checkpointreward",
"targetField": "id",
"format": "",
"description": "关卡奖励ID",
})
continue
if field in reward_fields:
fmt = detect_reward_format(sample_values.get(field, []))
if not fmt:
fmt = "item_id_num"
desc_map = {
"rewards": "奖励道具",
"rewards2": "付费奖励道具",
"pass_rewards": "首通奖励道具",
"activateRewards": "激活奖励道具",
"reward": "宝箱奖励道具",
}
desc = desc_map.get(field, f"{field}奖励道具")
if table_name == "gacha" and field == "rewards":
relations.append({
"field": field,
"target": "hero",
"targetField": "id",
"format": "hero_id",
"description": "召唤产出英雄(格式: hero_英雄ID)",
})
else:
relations.append({
"field": field,
"target": "prop",
"targetField": "id",
"format": fmt,
"description": f"{desc}(格式: {fmt})" if fmt else desc,
})
continue
if field in consume_fields:
fmt = detect_consumes_format(sample_values.get(field, []))
desc_map = {
"consumes": "消耗道具",
"lostConsumes": "失败损失道具",
}
desc = desc_map.get(field, field)
relations.append({
"field": field,
"target": "prop",
"targetField": "id",
"format": fmt if fmt else "item_id_num",
"description": f"{desc}(格式: item_id_num)" if fmt else f"{desc}(格式: item_id_num)",
})
continue
if field in monster_fields:
fmt = detect_monster_format(sample_values.get(field, []))
desc_map = {
"monsters": "怪物列表",
"enemy_ids": "敌人列表",
}
desc = desc_map.get(field, field)
format_desc = f"(格式: {fmt})" if fmt else ""
relations.append({
"field": field,
"target": "enemy",
"targetField": "id",
"format": fmt,
"description": f"{desc}{format_desc}",
})
continue
if field in time_limit_fields:
relations.append({
"field": field,
"target": "enemy",
"targetField": "id",
"format": "id_lv_num_hp",
"description": "限时Boss(格式: 敌人ID_等级_数量_血量百分比)",
})
continue
if field in equip_fields:
relations.append({
"field": field,
"target": "equip",
"targetField": "id",
"format": "",
"description": "装备槽位装备ID列表",
})
continue
if field in skill_fields:
relations.append({
"field": field,
"target": "skill",
"targetField": "id",
"format": "",
"description": "技能ID列表",
})
continue
if field in hero_fields:
relations.append({
"field": field,
"target": "hero",
"targetField": "id",
"format": "",
"description": "关联英雄ID列表",
})
continue
if field in quest_fields:
relations.append({
"field": field,
"target": "quest",
"targetField": "id",
"format": "",
"description": "关联任务ID列表",
})
continue
if field in attr_fields:
relations.append({
"field": field,
"target": "attribute",
"targetField": "id",
"format": "",
"description": "关联属性ID",
})
continue
if field in shard_fields:
relations.append({
"field": field,
"target": "prop",
"targetField": "id",
"format": "",
"description": "碎片道具ID",
})
continue
if field in compose_fields:
relations.append({
"field": field,
"target": "prop",
"targetField": "id",
"format": "",
"description": "合成材料道具ID列表",
})
continue
if field in next_fields:
relations.append({
"field": field,
"target": table_name,
"targetField": "id",
"format": "",
"description": "下一个ID(自引用)",
})
continue
if field in recharge_fields:
relations.append({
"field": field,
"target": "recharge",
"targetField": "id",
"format": "",
"description": "关联充值档位ID",
})
continue
if field in product_fields:
relations.append({
"field": field,
"target": "prop",
"targetField": "id",
"format": "",
"description": "商品道具ID",
})
continue
if field in out_enemy_fields:
relations.append({
"field": field,
"target": "map_act",
"targetField": "id",
"format": "",
"description": "出怪波次配置表名",
})
continue
if field in scene_fight_fields:
relations.append({
"field": field,
"target": "fight_sample",
"targetField": "id",
"format": "",
"description": "关联战斗配置表名",
})
continue
if field in scene_map_fields:
relations.append({
"field": field,
"target": "map",
"targetField": "id",
"format": "",
"description": "关联地图配置表名",
})
continue
if re.match(r"^.+Id$", field) and field != "Id":
prefix = field[:-2]
for known_table in TABLE_NAME_TO_CN:
if known_table.lower() == prefix.lower():
relations.append({
"field": field,
"target": known_table,
"targetField": "id",
"format": "",
"description": f"关联{TABLE_NAME_TO_CN[known_table]}ID",
})
break
return relations
SUSPICIOUS_ID_PATTERNS = [
(re.compile(r"^.+Id$"), "可能引用其他表的ID字段(驼峰)"),
(re.compile(r"^.+_id$"), "可能引用其他表的ID字段(下划线)"),
(re.compile(r"^.+_ids$"), "可能引用其他表的ID列表(下划线)"),
(re.compile(r"^.+_Ids$"), "可能引用其他表的ID列表(驼峰)"),
]
def analyze_gaps(all_tables, table_fields_map):
issues = []
ai_prompt_parts = []
tables_not_in_info = [t["name"] for t in all_tables if t["displayName"] == t["name"]]
if tables_not_in_info:
issues.append({
"type": "missing_display_name",
"severity": "warning",
"title": "以下表缺少中文名和描述",
"items": tables_not_in_info,
})
ai_prompt_parts.append(
f"以下表缺少中文名和描述,请补全 displayName 和 description:\n"
+ "\n".join(f" - {t}" for t in tables_not_in_info)
)
no_relation_tables = [t["name"] for t in all_tables if len(t["relations"]) == 0]
if no_relation_tables:
issues.append({
"type": "no_relations",
"severity": "info",
"title": "以下表没有任何引用关系(可能是叶子表或需人工检查)",
"items": no_relation_tables,
})
unrecognized_fields = []
for table_name, fields in table_fields_map.items():
table_entry = next((t for t in all_tables if t["name"] == table_name), None)
if not table_entry:
continue
related_fields = {r["field"] for r in table_entry["relations"]}
for field in fields:
if field in related_fields:
continue
for pattern, desc in SUSPICIOUS_ID_PATTERNS:
if pattern.match(field):
unrecognized_fields.append(f" {table_name}.{field} - {desc}")
break
if unrecognized_fields:
issues.append({
"type": "unrecognized_fields",
"severity": "warning",
"title": "以下字段可能是引用关系但脚本未能识别",
"items": unrecognized_fields,
})
ai_prompt_parts.append(
"以下字段可能是引用关系但脚本未能识别,请分析并补全 relations:\n"
+ "\n".join(unrecognized_fields)
)
tables_with_missing_fields = []
for table in all_tables:
if table["displayName"] == table["name"] and len(table["relations"]) == 0:
fields = table_fields_map.get(table["name"], [])
if fields:
tables_with_missing_fields.append(
f" {table['name']} (字段: {', '.join(fields[:8])}{'...' if len(fields) > 8 else ''})"
)
if tables_with_missing_fields:
issues.append({
"type": "fully_missing",
"severity": "error",
"title": "以下表完全未配置(无中文名、无描述、无引用关系)",
"items": tables_with_missing_fields,
})
ai_prompt_parts.append(
"以下表完全未配置,请补全 displayName、description 和 relations:\n"
+ "\n".join(tables_with_missing_fields)
)
return issues, ai_prompt_parts
def main():
args = parse_args()
config_dir = Path(args.config_dir) if args.config_dir else DEFAULT_CONFIG_DIR
output_file = Path(args.output) if args.output else DEFAULT_OUTPUT
table_info_file = Path(args.table_info) if args.table_info else DEFAULT_TABLE_INFO
table_info = load_table_info(table_info_file)
all_tables = []
table_fields_map = {}
json_files = sorted(config_dir.glob("*.json"))
for filepath in json_files:
table_name = filepath.stem
fields = extract_fields_from_json(filepath)
if not fields:
continue
table_fields_map[table_name] = fields
sample_values = {}
data = load_json_file(filepath)
if isinstance(data, list) and len(data) > 0:
first_row = data[0]
for field in fields:
if field in first_row:
val = first_row[field]
if isinstance(val, list):
sample_values[field] = val
else:
sample_values[field] = [val] if val is not None else []
display_name, description = table_info.get(table_name, (table_name, ""))
relations = build_relations(table_name, fields, sample_values)
all_tables.append({
"name": table_name,
"displayName": display_name,
"description": description,
"fileExtension": ".xlsx",
"relations": relations,
})
txt_files = sorted(config_dir.glob("*.txt"))
for filepath in txt_files:
table_name = filepath.stem
if table_name in table_info:
display_name, description = table_info[table_name]
else:
display_name, description = table_name, ""
all_tables.append({
"name": table_name,
"displayName": display_name,
"description": description,
"fileExtension": ".txt",
"relations": [],
})
output = {"tables": all_tables}
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, "w", encoding="utf-8") as f:
json.dump(output, f, ensure_ascii=False, indent=4)
total_tables = len(all_tables)
total_relations = sum(len(t['relations']) for t in all_tables)
tables_with_rels = sum(1 for t in all_tables if t['relations'])
print("=" * 60)
print(" ConfigLinkData.json 生成完成")
print("=" * 60)
print(f" 输出文件: {output_file}")
print(f" 总表数: {total_tables}")
print(f" 有引用关系的表: {tables_with_rels}")
print(f" 总引用关系数: {total_relations}")
print()
issues, ai_prompt_parts = analyze_gaps(all_tables, table_fields_map)
if not issues:
print(" 所有表均已完整配置,无需要补全的内容。")
else:
print("-" * 60)
print(" 查漏补缺报告")
print("-" * 60)
for issue in issues:
severity_icon = {"error": "[!]", "warning": "[*]", "info": "[i]"}.get(issue["severity"], "")
print(f"\n {severity_icon} {issue['title']} ({len(issue['items'])}项)")
for item in issue["items"]:
print(f" {item}")
if ai_prompt_parts:
print()
print("=" * 60)
print(" AI 补全提示词(可复制发送给 AI")
print("=" * 60)
print()
print("请帮我补全以下配置表联动关系配置:")
print()
for part in ai_prompt_parts:
print(part)
print()
print("请直接返回修改后的 JSON 配置。")
print()
if __name__ == "__main__":
main()