weapon_config_manager.py 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 武器配置管理器
  5. 从config_manager.py中提取的武器相关配置管理功能
  6. 支持从Excel读取武器配置并与现有JSON配置合并
  7. """
  8. import json
  9. import os
  10. from pathlib import Path
  11. from datetime import datetime
  12. try:
  13. import pandas as pd
  14. PANDAS_AVAILABLE = True
  15. except ImportError:
  16. PANDAS_AVAILABLE = False
  17. print("警告: pandas未安装,无法处理Excel文件")
  18. class WeaponConfigManager:
  19. """武器配置管理器"""
  20. def __init__(self, excel_file_path=None, json_file_path=None):
  21. """初始化武器配置管理器
  22. Args:
  23. excel_file_path: Excel配置文件路径
  24. json_file_path: JSON配置文件路径
  25. """
  26. self.script_dir = Path(__file__).parent
  27. # 设置默认路径
  28. if excel_file_path is None:
  29. self.excel_file = self.script_dir / "方块武器配置" / "方块武器配置表.xlsx"
  30. else:
  31. self.excel_file = Path(excel_file_path)
  32. if json_file_path is None:
  33. self.json_file = self.script_dir.parent / "weapons.json"
  34. else:
  35. self.json_file = Path(json_file_path)
  36. print(f"Excel文件路径: {self.excel_file}")
  37. print(f"JSON文件路径: {self.json_file}")
  38. # 武器配置映射
  39. self.weapon_mapping = {
  40. 'format_type': 'horizontal',
  41. 'param_types': {
  42. 'ID': str,
  43. '名称': str,
  44. '类型': str,
  45. '权重': int,
  46. '伤害': int,
  47. '射速': float,
  48. '射程': int,
  49. '子弹速度': int,
  50. '稀有度伤害倍率': str, # 逗号分隔的数组字符串
  51. '解锁条件关卡': int, # 新增解锁条件关卡字段
  52. # 方块价格配置字段
  53. '基础每格成本': int,
  54. 'I形状成本': int,
  55. 'H-I形状成本': int,
  56. 'L形状成本': int,
  57. 'S形状成本': int,
  58. 'D-T形状成本': int,
  59. # 英文字段支持
  60. 'id': str,
  61. 'name': str,
  62. 'type': str,
  63. 'weight': int,
  64. 'damage': int,
  65. 'fireRate': float,
  66. 'range': int,
  67. 'bulletSpeed': int,
  68. 'rarityDamageMultipliers': str, # 逗号分隔的数组字符串
  69. 'unlockLevel': int, # 英文版解锁条件关卡字段
  70. 'baseCost': int,
  71. 'I_shape_cost': int,
  72. 'HI_shape_cost': int,
  73. 'L_shape_cost': int,
  74. 'S_shape_cost': int,
  75. 'DT_shape_cost': int
  76. }
  77. }
  78. def load_existing_json_config(self):
  79. """加载现有的JSON配置文件"""
  80. try:
  81. if self.json_file.exists():
  82. with open(self.json_file, 'r', encoding='utf-8') as f:
  83. config = json.load(f)
  84. print(f"成功加载现有JSON配置,包含 {len(config.get('weapons', []))} 个武器")
  85. return config
  86. else:
  87. print(f"JSON文件不存在,将创建新配置: {self.json_file}")
  88. return {'weapons': [], 'blockSizes': []}
  89. except Exception as e:
  90. print(f"加载JSON配置失败: {e}")
  91. return {'weapons': [], 'blockSizes': []}
  92. def read_excel_config(self):
  93. """读取Excel配置文件"""
  94. if not PANDAS_AVAILABLE:
  95. raise Exception("pandas未安装,无法读取Excel文件")
  96. if not self.excel_file.exists():
  97. raise Exception(f"Excel文件不存在: {self.excel_file}")
  98. try:
  99. # 读取所有工作表
  100. all_sheets = pd.read_excel(self.excel_file, sheet_name=None)
  101. print(f"成功读取Excel文件,包含工作表: {list(all_sheets.keys())}")
  102. return all_sheets
  103. except Exception as e:
  104. raise Exception(f"读取Excel文件失败: {e}")
  105. def parse_weapon_multi_sheet_data(self, all_sheets_data):
  106. """解析武器配置表的多工作表数据"""
  107. weapons_config = {'weapons': []}
  108. try:
  109. # 解析武器基础配置工作表
  110. base_sheet = None
  111. for sheet_name in ['武器基础配置', 'Weapon Config', 'weapons', '武器配置']:
  112. if sheet_name in all_sheets_data:
  113. base_sheet = all_sheets_data[sheet_name]
  114. break
  115. if base_sheet is not None:
  116. base_config = self.parse_config_data(base_sheet)
  117. if 'items' in base_config:
  118. weapons_config['weapons'] = base_config['items']
  119. print(f"成功解析武器基础配置,共{len(base_config['items'])}个武器")
  120. # 解析武器升级费用配置工作表
  121. upgrade_cost_sheet = None
  122. for sheet_name in ['武器升级费用配置', 'Weapon Upgrade Cost', 'upgrade_costs', '升级费用']:
  123. if sheet_name in all_sheets_data:
  124. upgrade_cost_sheet = all_sheets_data[sheet_name]
  125. print(f"找到升级费用配置工作表: {sheet_name}")
  126. break
  127. if upgrade_cost_sheet is not None:
  128. self._parse_upgrade_cost_data(upgrade_cost_sheet, weapons_config['weapons'])
  129. # 解析游戏内成本配置工作表
  130. cost_sheet = None
  131. for sheet_name in ['游戏内成本配置', 'In Game Cost', 'cost_config', '成本配置']:
  132. if sheet_name in all_sheets_data:
  133. cost_sheet = all_sheets_data[sheet_name]
  134. print(f"找到游戏内成本配置工作表: {sheet_name}")
  135. break
  136. if cost_sheet is not None:
  137. self._parse_cost_config_data(cost_sheet, weapons_config['weapons'])
  138. # 解析方块形状配置工作表
  139. block_shape_sheet = None
  140. for sheet_name in ['方块形状配置', 'Block Shape Config', 'block_shapes', '形状配置']:
  141. if sheet_name in all_sheets_data:
  142. block_shape_sheet = all_sheets_data[sheet_name]
  143. print(f"找到方块形状配置工作表: {sheet_name}")
  144. break
  145. if block_shape_sheet is not None:
  146. weapons_config['blockSizes'] = self._parse_block_shape_data(block_shape_sheet)
  147. # 注意:稀有度权重配置和稀有度伤害倍率配置现在已经移到武器基础配置表中
  148. # 不再需要单独的工作表解析
  149. return weapons_config
  150. except Exception as e:
  151. print(f"解析武器配置失败: {e}")
  152. return {'weapons': []}
  153. def parse_config_data(self, df):
  154. """解析配置数据"""
  155. try:
  156. items = []
  157. # 检查第一行是否为表头
  158. first_row = df.iloc[0] if len(df) > 0 else None
  159. is_header = False
  160. if first_row is not None:
  161. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  162. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  163. is_header = True
  164. print(f"检测到表头行,第一列内容: {first_cell}")
  165. for index, row in df.iterrows():
  166. if is_header and index == 0: # 跳过表头
  167. continue
  168. # 转换行数据为字典
  169. item = {}
  170. for col_index, value in enumerate(row):
  171. if col_index < len(df.columns):
  172. col_name = df.columns[col_index]
  173. if pd.notna(value) and str(value).strip():
  174. # 根据映射转换数据类型
  175. param_type = self.weapon_mapping['param_types'].get(col_name, str)
  176. try:
  177. if param_type == int:
  178. item[col_name] = int(float(value))
  179. elif param_type == float:
  180. item[col_name] = float(value)
  181. else:
  182. # 特殊处理稀有度伤害倍率字段
  183. if col_name in ['稀有度伤害倍率', 'rarityDamageMultipliers']:
  184. # 将逗号分隔的字符串转换为浮点数数组
  185. multipliers_str = str(value).strip()
  186. if multipliers_str:
  187. try:
  188. multipliers = [float(x.strip()) for x in multipliers_str.split(',') if x.strip()]
  189. item[col_name] = multipliers
  190. except ValueError:
  191. # 如果解析失败,使用默认值
  192. item[col_name] = [1.0, 1.5, 2.25, 8.0]
  193. else:
  194. item[col_name] = [1.0, 1.5, 2.25, 8.0]
  195. else:
  196. item[col_name] = str(value).strip()
  197. except (ValueError, TypeError):
  198. # 特殊处理稀有度伤害倍率字段的异常情况
  199. if col_name in ['稀有度伤害倍率', 'rarityDamageMultipliers']:
  200. item[col_name] = [1.0, 1.5, 2.25, 8.0]
  201. else:
  202. item[col_name] = str(value).strip()
  203. # 检查是否有有效的武器ID
  204. weapon_id = item.get('ID') or item.get('id') or item.get('武器ID')
  205. if weapon_id and str(weapon_id).strip():
  206. items.append(item)
  207. return {'items': items}
  208. except Exception as e:
  209. print(f"解析配置数据失败: {e}")
  210. return {'items': []}
  211. def _parse_upgrade_cost_data(self, upgrade_cost_sheet, weapons_list):
  212. """解析升级费用配置数据"""
  213. try:
  214. print(f"开始处理升级费用配置,工作表行数: {len(upgrade_cost_sheet)}")
  215. upgrade_cost_data = []
  216. # 检查第一行是否为表头
  217. first_row = upgrade_cost_sheet.iloc[0] if len(upgrade_cost_sheet) > 0 else None
  218. is_header = False
  219. if first_row is not None:
  220. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  221. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  222. is_header = True
  223. print(f"检测到表头行,第一列内容: {first_cell}")
  224. for index, row in upgrade_cost_sheet.iterrows():
  225. if is_header and index == 0: # 跳过表头
  226. continue
  227. # 支持多种武器ID字段名
  228. weapon_id = None
  229. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  230. if id_field in row and pd.notna(row[id_field]):
  231. weapon_id = row[id_field]
  232. break
  233. if weapon_id is None:
  234. weapon_id = row.iloc[0] if len(row) > 0 else None
  235. if weapon_id and str(weapon_id).strip():
  236. upgrade_levels = {}
  237. # 从第5列开始是等级1-10的费用,从第15列开始是等级1-10的伤害
  238. for level in range(1, 11):
  239. cost_col_index = 4 + (level - 1)
  240. damage_col_index = 14 + (level - 1)
  241. level_config = {}
  242. # 处理费用
  243. if cost_col_index < len(row):
  244. cost = row.iloc[cost_col_index]
  245. if cost and str(cost).strip() and str(cost) != 'nan':
  246. try:
  247. level_config['cost'] = int(float(cost))
  248. except (ValueError, TypeError):
  249. pass
  250. # 处理伤害
  251. if damage_col_index < len(row):
  252. damage = row.iloc[damage_col_index]
  253. if damage and str(damage).strip() and str(damage) != 'nan':
  254. try:
  255. level_config['damage'] = int(float(damage))
  256. except (ValueError, TypeError):
  257. pass
  258. if level_config:
  259. upgrade_levels[str(level)] = level_config
  260. if upgrade_levels:
  261. upgrade_cost_data.append({
  262. 'weapon_id': str(weapon_id).strip(),
  263. 'levels': upgrade_levels
  264. })
  265. # 将升级费用配置合并到武器数据中
  266. for weapon in weapons_list:
  267. weapon_id = weapon.get('ID', '') or weapon.get('id', '')
  268. if weapon_id:
  269. matching_upgrade = None
  270. for upgrade_data in upgrade_cost_data:
  271. if upgrade_data['weapon_id'] == weapon_id:
  272. matching_upgrade = upgrade_data
  273. break
  274. if matching_upgrade:
  275. weapon['upgradeConfig'] = {
  276. 'maxLevel': 10,
  277. 'levels': matching_upgrade['levels']
  278. }
  279. print(f"✓ 为武器 {weapon_id} 添加了升级费用配置")
  280. except Exception as e:
  281. print(f"解析升级费用配置失败: {e}")
  282. def _parse_cost_config_data(self, cost_sheet, weapons_list):
  283. """解析游戏内成本配置数据"""
  284. try:
  285. print(f"开始处理游戏内成本配置,工作表行数: {len(cost_sheet)}")
  286. # 检查第一行是否为表头
  287. first_row = cost_sheet.iloc[0] if len(cost_sheet) > 0 else None
  288. is_header = False
  289. if first_row is not None:
  290. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  291. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  292. is_header = True
  293. for index, row in cost_sheet.iterrows():
  294. if is_header and index == 0: # 跳过表头
  295. continue
  296. # 获取武器ID
  297. weapon_id = None
  298. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  299. if id_field in row and pd.notna(row[id_field]):
  300. weapon_id = str(row[id_field]).strip()
  301. break
  302. if weapon_id is None:
  303. weapon_id = str(row.iloc[0]).strip() if len(row) > 0 else None
  304. if weapon_id:
  305. # 查找对应的武器并添加成本配置
  306. for weapon in weapons_list:
  307. w_id = weapon.get('ID', '') or weapon.get('id', '')
  308. if w_id == weapon_id:
  309. # 构建成本配置
  310. base_cost = 5 # 默认基础成本
  311. shape_costs = {}
  312. # 读取基础成本
  313. for field in ['武器基础售价', 'baseCost', '基础成本']:
  314. if field in row and pd.notna(row[field]):
  315. try:
  316. base_cost = int(float(row[field]))
  317. break
  318. except (ValueError, TypeError):
  319. pass
  320. # 读取各形状成本
  321. shape_fields = {
  322. 'I': ['I形状成本', 'I形状', 'I_shape', 'I'],
  323. 'H-I': ['H-I形状成本', 'H-I形状', 'HI_shape', 'H-I'],
  324. 'L': ['L形状成本', 'L形状', 'L_shape', 'L'],
  325. 'S': ['S形状成本', 'S形状', 'S_shape', 'S'],
  326. 'D-T': ['D-T形状成本', 'D-T形状', 'DT_shape', 'D-T'],
  327. 'L2': ['L2形状成本', 'L2形状', 'L2_shape', 'L2'],
  328. 'L3': ['L3形状成本', 'L3形状', 'L3_shape', 'L3'],
  329. 'L4': ['L4形状成本', 'L4形状', 'L4_shape', 'L4'],
  330. 'F-S': ['F-S形状成本', 'F-S形状', 'FS_shape', 'F-S'],
  331. 'T': ['T形状成本', 'T形状', 'T_shape', 'T']
  332. }
  333. for shape_key, field_names in shape_fields.items():
  334. for field_name in field_names:
  335. if field_name in row and pd.notna(row[field_name]):
  336. try:
  337. shape_costs[shape_key] = int(float(row[field_name]))
  338. break
  339. except (ValueError, TypeError):
  340. pass
  341. weapon['inGameCostConfig'] = {
  342. 'baseCost': base_cost,
  343. 'shapeCosts': shape_costs
  344. }
  345. print(f"✓ 为武器 {weapon_id} 添加了游戏内成本配置")
  346. break
  347. except Exception as e:
  348. print(f"解析游戏内成本配置失败: {e}")
  349. def _parse_block_shape_data(self, block_shape_sheet):
  350. """解析方块形状配置数据"""
  351. try:
  352. block_shapes = []
  353. # 检查第一行是否为表头
  354. first_row = block_shape_sheet.iloc[0] if len(block_shape_sheet) > 0 else None
  355. is_header = False
  356. if first_row is not None:
  357. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  358. if first_cell in ['ID', 'id', '形状ID', 'shape_id']:
  359. is_header = True
  360. print(f"检测到方块形状配置表头行,第一列内容: {first_cell}")
  361. for index, row in block_shape_sheet.iterrows():
  362. if is_header and index == 0: # 跳过表头
  363. continue
  364. # 获取方块形状数据
  365. shape_id = None
  366. shape_name = None
  367. shape_matrix = None
  368. grid_count = None
  369. cost_multiplier = None
  370. description = None
  371. # 获取ID
  372. for field in ['ID', 'id', '形状ID', 'shape_id']:
  373. if field in row and pd.notna(row[field]):
  374. shape_id = str(row[field]).strip()
  375. break
  376. if shape_id is None:
  377. shape_id = str(row.iloc[0]).strip() if len(row) > 0 else None
  378. # 获取名称
  379. for field in ['名称', 'name', 'Name', '形状名称']:
  380. if field in row and pd.notna(row[field]):
  381. shape_name = str(row[field]).strip()
  382. break
  383. if shape_name is None and len(row) > 1:
  384. shape_name = str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else None
  385. # 获取形状矩阵
  386. for field in ['形状矩阵', 'shape', 'matrix', '矩阵']:
  387. if field in row and pd.notna(row[field]):
  388. shape_matrix = str(row[field]).strip()
  389. break
  390. if shape_matrix is None and len(row) > 2:
  391. shape_matrix = str(row.iloc[2]).strip() if pd.notna(row.iloc[2]) else None
  392. # 获取占用格数
  393. for field in ['占用格数', 'gridCount', 'grid_count', '格数']:
  394. if field in row and pd.notna(row[field]):
  395. try:
  396. grid_count = int(float(row[field]))
  397. break
  398. except (ValueError, TypeError):
  399. pass
  400. if grid_count is None and len(row) > 3:
  401. try:
  402. grid_count = int(float(row.iloc[3])) if pd.notna(row.iloc[3]) else None
  403. except (ValueError, TypeError):
  404. pass
  405. # 获取成本倍数
  406. for field in ['成本倍数', 'costMultiplier', 'cost_multiplier', '倍数']:
  407. if field in row and pd.notna(row[field]):
  408. try:
  409. cost_multiplier = int(float(row[field]))
  410. break
  411. except (ValueError, TypeError):
  412. pass
  413. if cost_multiplier is None and len(row) > 4:
  414. try:
  415. cost_multiplier = int(float(row.iloc[4])) if pd.notna(row.iloc[4]) else None
  416. except (ValueError, TypeError):
  417. pass
  418. # 获取描述
  419. for field in ['描述', 'description', 'Description', '说明']:
  420. if field in row and pd.notna(row[field]):
  421. description = str(row[field]).strip()
  422. break
  423. if description is None and len(row) > 5:
  424. description = str(row.iloc[5]).strip() if pd.notna(row.iloc[5]) else None
  425. # 如果有有效的形状ID,则创建形状配置
  426. if shape_id:
  427. # 解析形状矩阵
  428. shape_array = self._parse_shape_matrix(shape_matrix)
  429. block_shape = {
  430. "id": shape_id,
  431. "name": shape_name or shape_id,
  432. "shape": shape_array,
  433. "gridCount": grid_count or len([cell for row in shape_array for cell in row if cell == 1]),
  434. "costMultiplier": cost_multiplier or grid_count or 1,
  435. "description": description or f"{shape_name or shape_id}形状"
  436. }
  437. block_shapes.append(block_shape)
  438. print(f"✓ 添加方块形状配置: {shape_id} ({shape_name})")
  439. return block_shapes
  440. except Exception as e:
  441. print(f"解析方块形状配置失败: {e}")
  442. return []
  443. def _parse_shape_matrix(self, shape_matrix_str):
  444. """解析形状矩阵字符串为二维数组"""
  445. try:
  446. if not shape_matrix_str:
  447. return [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
  448. shape_matrix_str = str(shape_matrix_str).strip()
  449. # 尝试解析JSON格式的矩阵字符串,如 "[0, 1, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0]"
  450. if '[' in shape_matrix_str and ']' in shape_matrix_str:
  451. try:
  452. # 添加外层方括号使其成为有效的JSON数组
  453. json_str = '[' + shape_matrix_str + ']'
  454. import json
  455. shape_array = json.loads(json_str)
  456. # 确保是4x4矩阵
  457. while len(shape_array) < 4:
  458. shape_array.append([0, 0, 0, 0])
  459. for i in range(len(shape_array)):
  460. if len(shape_array[i]) < 4:
  461. shape_array[i].extend([0] * (4 - len(shape_array[i])))
  462. shape_array[i] = shape_array[i][:4]
  463. return shape_array[:4]
  464. except (json.JSONDecodeError, ValueError) as e:
  465. print(f"JSON解析失败: {e}, 尝试其他解析方式")
  466. # 按换行符分割行(原有逻辑保留作为备用)
  467. lines = shape_matrix_str.split('\n')
  468. shape_array = []
  469. for line in lines:
  470. line = line.strip()
  471. if line:
  472. # 将每个字符转换为数字
  473. row = [int(char) for char in line if char in '01']
  474. # 确保每行有4个元素
  475. while len(row) < 4:
  476. row.append(0)
  477. shape_array.append(row[:4]) # 只取前4个元素
  478. # 确保有4行
  479. while len(shape_array) < 4:
  480. shape_array.append([0, 0, 0, 0])
  481. return shape_array[:4] # 只取前4行
  482. except Exception as e:
  483. print(f"解析形状矩阵失败: {e}, 使用默认矩阵")
  484. return [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
  485. def _parse_rarity_weights_data(self, rarity_weights_sheet):
  486. """解析稀有度权重配置数据"""
  487. try:
  488. print(f"开始处理稀有度权重配置,工作表行数: {len(rarity_weights_sheet)}")
  489. rarity_weights = {}
  490. # 检查第一行是否为表头
  491. first_row = rarity_weights_sheet.iloc[0] if len(rarity_weights_sheet) > 0 else None
  492. is_header = False
  493. if first_row is not None:
  494. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  495. if first_cell in ['稀有度', 'Rarity', 'rarity', '等级']:
  496. is_header = True
  497. print(f"检测到表头行,第一列内容: {first_cell}")
  498. for index, row in rarity_weights_sheet.iterrows():
  499. if is_header and index == 0: # 跳过表头
  500. continue
  501. # 获取稀有度名称
  502. rarity_name = None
  503. for field in ['稀有度', 'Rarity', 'rarity', '等级']:
  504. if field in row and pd.notna(row[field]):
  505. rarity_name = str(row[field]).strip().lower()
  506. break
  507. if rarity_name is None and len(row) > 0:
  508. rarity_name = str(row.iloc[0]).strip().lower() if pd.notna(row.iloc[0]) else None
  509. # 获取权重值
  510. weight = None
  511. for field in ['权重', 'Weight', 'weight', '值']:
  512. if field in row and pd.notna(row[field]):
  513. try:
  514. weight = int(float(row[field]))
  515. break
  516. except (ValueError, TypeError):
  517. pass
  518. if weight is None and len(row) > 1:
  519. try:
  520. weight = int(float(row.iloc[1])) if pd.notna(row.iloc[1]) else None
  521. except (ValueError, TypeError):
  522. pass
  523. # 映射中文稀有度名称到英文
  524. rarity_mapping = {
  525. '普通': 'common',
  526. '稀有': 'uncommon',
  527. '史诗': 'rare',
  528. '传说': 'epic',
  529. 'common': 'common',
  530. 'uncommon': 'uncommon',
  531. 'rare': 'rare',
  532. 'epic': 'epic'
  533. }
  534. if rarity_name and weight is not None:
  535. mapped_rarity = rarity_mapping.get(rarity_name, rarity_name)
  536. rarity_weights[mapped_rarity] = weight
  537. print(f"✓ 添加稀有度权重配置: {mapped_rarity} = {weight}")
  538. return rarity_weights
  539. except Exception as e:
  540. print(f"解析稀有度权重配置失败: {e}")
  541. return {}
  542. def _parse_rarity_damage_multipliers_data(self, rarity_damage_multipliers_sheet):
  543. """解析稀有度伤害倍率配置数据"""
  544. try:
  545. print(f"开始处理稀有度伤害倍率配置,工作表行数: {len(rarity_damage_multipliers_sheet)}")
  546. damage_multipliers = []
  547. # 检查第一行是否为表头
  548. first_row = rarity_damage_multipliers_sheet.iloc[0] if len(rarity_damage_multipliers_sheet) > 0 else None
  549. is_header = False
  550. if first_row is not None:
  551. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  552. if first_cell in ['配置项', 'Config Item', 'config_item', '等级', 'Level', 'level', '稀有度等级']:
  553. is_header = True
  554. print(f"检测到表头行,第一列内容: {first_cell}")
  555. for index, row in rarity_damage_multipliers_sheet.iterrows():
  556. if is_header and index == 0: # 跳过表头
  557. continue
  558. # 获取配置项名称
  559. config_item = None
  560. for field in ['配置项', 'Config Item', 'config_item']:
  561. if field in row and pd.notna(row[field]):
  562. config_item = str(row[field]).strip()
  563. break
  564. if config_item is None and len(row) > 0:
  565. config_item = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else None
  566. # 如果找到rarityDamageMultipliers配置项
  567. if config_item == 'rarityDamageMultipliers':
  568. # 获取值字段
  569. multipliers_str = None
  570. for field in ['值', 'Value', 'value']:
  571. if field in row and pd.notna(row[field]):
  572. multipliers_str = str(row[field]).strip()
  573. break
  574. if multipliers_str is None and len(row) > 1:
  575. multipliers_str = str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else None
  576. if multipliers_str:
  577. try:
  578. # 解析逗号分隔的数组字符串
  579. multipliers_list = [float(x.strip()) for x in multipliers_str.split(',')]
  580. damage_multipliers = multipliers_list
  581. print(f"✓ 解析稀有度伤害倍率配置: {damage_multipliers}")
  582. break
  583. except (ValueError, TypeError) as e:
  584. print(f"解析倍率数组失败: {e}, 字符串: {multipliers_str}")
  585. # 如果没有找到配置或解析失败,尝试旧格式解析
  586. if not damage_multipliers:
  587. print("未找到新格式配置,尝试解析旧格式...")
  588. level_multipliers = {}
  589. for index, row in rarity_damage_multipliers_sheet.iterrows():
  590. if is_header and index == 0: # 跳过表头
  591. continue
  592. # 获取稀有度等级
  593. level = None
  594. for field in ['等级', 'Level', 'level', '稀有度等级']:
  595. if field in row and pd.notna(row[field]):
  596. try:
  597. level_str = str(row[field]).strip()
  598. if level_str.startswith('等级'):
  599. level = int(level_str.replace('等级', ''))
  600. else:
  601. level = int(float(row[field]))
  602. break
  603. except (ValueError, TypeError):
  604. pass
  605. if level is None and len(row) > 0:
  606. try:
  607. first_cell = str(row.iloc[0]).strip()
  608. if first_cell.startswith('等级'):
  609. level = int(first_cell.replace('等级', ''))
  610. else:
  611. level = int(float(row.iloc[0])) if pd.notna(row.iloc[0]) else None
  612. except (ValueError, TypeError):
  613. pass
  614. # 获取伤害倍率
  615. multiplier = None
  616. for field in ['伤害倍率', 'Damage Multiplier', 'multiplier', '倍率', '值', 'Value', 'value']:
  617. if field in row and pd.notna(row[field]):
  618. try:
  619. multiplier = float(row[field])
  620. break
  621. except (ValueError, TypeError):
  622. pass
  623. if multiplier is None and len(row) > 1:
  624. try:
  625. multiplier = float(row.iloc[1]) if pd.notna(row.iloc[1]) else None
  626. except (ValueError, TypeError):
  627. pass
  628. if level is not None and multiplier is not None:
  629. level_multipliers[level] = multiplier
  630. print(f"✓ 添加稀有度伤害倍率配置: 等级{level} = {multiplier}倍")
  631. # 将字典转换为按等级排序的数组
  632. if level_multipliers:
  633. max_level = max(level_multipliers.keys())
  634. for i in range(max_level + 1):
  635. if i in level_multipliers:
  636. damage_multipliers.append(level_multipliers[i])
  637. else:
  638. # 使用默认值
  639. default_multipliers = [1, 1.5, 2.25, 8]
  640. if i < len(default_multipliers):
  641. damage_multipliers.append(default_multipliers[i])
  642. else:
  643. damage_multipliers.append(1)
  644. # 如果仍然没有数据,使用默认值
  645. if not damage_multipliers:
  646. damage_multipliers = [1, 1.5, 2.25, 8]
  647. print("使用默认稀有度伤害倍率配置")
  648. return damage_multipliers
  649. except Exception as e:
  650. print(f"解析稀有度伤害倍率配置失败: {e}")
  651. return [1, 1.5, 2.25, 8] # 返回默认值
  652. def merge_weapon_configs(self, existing_config, excel_config):
  653. """合并现有JSON配置和Excel配置"""
  654. try:
  655. print("开始合并武器配置...")
  656. # 创建现有武器的映射表(按ID索引)
  657. existing_weapons_map = {}
  658. for weapon in existing_config.get('weapons', []):
  659. weapon_id = weapon.get('id')
  660. if weapon_id:
  661. existing_weapons_map[weapon_id] = weapon
  662. print(f"现有武器数量: {len(existing_weapons_map)}")
  663. print(f"Excel武器数量: {len(excel_config.get('weapons', []))}")
  664. # 处理Excel中的武器数据
  665. merged_weapons = []
  666. for excel_weapon in excel_config.get('weapons', []):
  667. weapon_id = excel_weapon.get('ID') or excel_weapon.get('id')
  668. if not weapon_id:
  669. continue
  670. # 转换Excel数据为标准格式
  671. converted_weapon = self._convert_weapon_data(
  672. excel_weapon,
  673. existing_weapons_map.get(weapon_id)
  674. )
  675. if converted_weapon:
  676. merged_weapons.append(converted_weapon)
  677. print(f"✓ 处理武器: {weapon_id}")
  678. # 添加Excel中没有但现有配置中存在的武器
  679. excel_weapon_ids = {w.get('ID') or w.get('id') for w in excel_config.get('weapons', [])}
  680. for weapon_id, existing_weapon in existing_weapons_map.items():
  681. if weapon_id not in excel_weapon_ids:
  682. merged_weapons.append(existing_weapon)
  683. print(f"✓ 保留现有武器: {weapon_id}")
  684. # 构建最终配置
  685. merged_config = existing_config.copy()
  686. merged_config['weapons'] = merged_weapons
  687. # 合并方块形状配置
  688. if 'blockSizes' in excel_config:
  689. merged_config['blockSizes'] = excel_config['blockSizes']
  690. print(f"✓ 更新方块形状配置,共{len(excel_config['blockSizes'])}个形状")
  691. # 保留重要的全局配置字段
  692. global_config_fields = ['rarityWeights', 'rarityDamageMultipliers']
  693. for field in global_config_fields:
  694. if field in existing_config:
  695. merged_config[field] = existing_config[field]
  696. print(f"✓ 保留全局配置: {field}")
  697. print(f"合并完成,最终武器数量: {len(merged_weapons)}")
  698. return merged_config
  699. except Exception as e:
  700. print(f"合并武器配置失败: {e}")
  701. return existing_config
  702. def _convert_weapon_data(self, item, existing_weapon=None):
  703. """转换武器数据格式"""
  704. try:
  705. # 支持中英文字段名
  706. weapon_id = item.get('id', item.get('ID', ''))
  707. weapon_name = item.get('name', item.get('名称', ''))
  708. if not weapon_id:
  709. print(f"跳过无效武器数据: 缺少武器ID - {item}")
  710. return None
  711. # 获取基础属性
  712. damage = item.get('damage', item.get('伤害', 10))
  713. fire_rate = item.get('fireRate', item.get('射速', 1.0))
  714. weapon_range = item.get('range', item.get('射程', 100))
  715. bullet_speed = item.get('bulletSpeed', item.get('子弹速度', 100))
  716. weapon_type = item.get('type', item.get('类型', ''))
  717. weight = item.get('weight', item.get('权重', 1))
  718. unlock_level = item.get('unlockLevel', item.get('解锁条件关卡', 1)) # 新增解锁条件关卡字段
  719. # 获取稀有度伤害倍率
  720. rarity_damage_multipliers = item.get('rarityDamageMultipliers', item.get('稀有度伤害倍率', [1.0, 1.5, 2.25, 8.0]))
  721. # 确保是数组格式
  722. if not isinstance(rarity_damage_multipliers, list):
  723. rarity_damage_multipliers = [1.0, 1.5, 2.25, 8.0]
  724. # 推断武器类型(如果为空)
  725. if not weapon_type:
  726. weapon_type = self._infer_weapon_type(weapon_id)
  727. # 设置默认权重(如果为空)
  728. if weight == 1:
  729. weight = 20 # 默认权重
  730. # 构建基础武器配置
  731. result = {
  732. 'id': weapon_id,
  733. 'name': weapon_name,
  734. 'type': weapon_type,
  735. 'weight': weight,
  736. 'unlockLevel': unlock_level, # 添加解锁条件关卡到结果中
  737. 'rarityDamageMultipliers': rarity_damage_multipliers,
  738. 'stats': {
  739. 'damage': damage,
  740. 'fireRate': fire_rate,
  741. 'range': weapon_range,
  742. 'bulletSpeed': min(bullet_speed, 50) # 限制子弹速度
  743. }
  744. }
  745. # 如果有现有武器配置,保留其bulletConfig和visualConfig
  746. if existing_weapon:
  747. if 'bulletConfig' in existing_weapon:
  748. result['bulletConfig'] = existing_weapon['bulletConfig']
  749. print(f"为武器 {weapon_id} 保留现有的bulletConfig")
  750. else:
  751. result['bulletConfig'] = self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range)
  752. if 'visualConfig' in existing_weapon:
  753. result['visualConfig'] = existing_weapon['visualConfig']
  754. print(f"为武器 {weapon_id} 保留现有的visualConfig")
  755. else:
  756. result['visualConfig'] = self._generate_visual_config(weapon_id, weapon_name)
  757. else:
  758. # 生成默认配置
  759. result['bulletConfig'] = self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range)
  760. result['visualConfig'] = self._generate_visual_config(weapon_id, weapon_name)
  761. # 添加升级配置(如果Excel中有)
  762. if 'upgradeConfig' in item:
  763. result['upgradeConfig'] = item['upgradeConfig']
  764. print(f"为武器 {weapon_id} 添加升级配置")
  765. # 添加游戏内成本配置(如果Excel中有)
  766. if 'inGameCostConfig' in item:
  767. result['inGameCostConfig'] = item['inGameCostConfig']
  768. print(f"为武器 {weapon_id} 添加游戏内成本配置")
  769. return result
  770. except Exception as e:
  771. print(f"转换武器数据失败: {e} - 数据: {item}")
  772. return None
  773. def _infer_weapon_type(self, weapon_id):
  774. """根据武器ID推断武器类型"""
  775. if 'shotgun' in weapon_id or 'cactus' in weapon_id:
  776. return 'shotgun'
  777. elif 'bomb' in weapon_id or 'pepper' in weapon_id:
  778. return 'explosive'
  779. elif 'missile' in weapon_id:
  780. return 'homing_missile'
  781. elif 'boomerang' in weapon_id:
  782. return 'boomerang'
  783. elif 'saw' in weapon_id:
  784. return 'ricochet_piercing'
  785. elif 'carrot' in weapon_id:
  786. return 'piercing'
  787. else:
  788. return 'single_shot'
  789. def _generate_bullet_config(self, weapon_id, weapon_type, damage, weapon_range):
  790. """生成子弹配置"""
  791. # 基础配置模板
  792. base_config = {
  793. 'count': {'type': 'single', 'amount': 1, 'spreadAngle': 0, 'burstCount': 1, 'burstDelay': 0},
  794. 'trajectory': {'type': 'straight', 'speed': 200, 'gravity': 0, 'arcHeight': 0, 'homingStrength': 0, 'homingDelay': 0},
  795. 'hitEffects': [{'type': 'normal_damage', 'priority': 1, 'damage': damage}],
  796. 'lifecycle': {'type': 'hit_destroy', 'maxLifetime': 5.0, 'penetration': 1, 'ricochetCount': 0, 'returnToOrigin': False},
  797. 'visual': {
  798. 'bulletImages': f'images/PlantsSprite/{sprite_id}',
  799. 'hitEffect': 'Animation/WeaponTx/tx0002/tx0002',
  800. 'trailEffect': True
  801. }
  802. }
  803. # 根据武器类型调整配置
  804. if weapon_type == 'shotgun':
  805. base_config['count'] = {'type': 'spread', 'amount': 5, 'spreadAngle': 30, 'burstCount': 1, 'burstDelay': 0}
  806. base_config['lifecycle']['type'] = 'range_limit'
  807. base_config['lifecycle']['maxRange'] = weapon_range * 2
  808. elif weapon_type == 'piercing':
  809. base_config['hitEffects'] = [{'type': 'pierce_damage', 'priority': 1, 'damage': damage, 'pierceCount': 999}]
  810. base_config['lifecycle'] = {'type': 'range_limit', 'maxLifetime': 5.0, 'penetration': 999, 'ricochetCount': 0, 'returnToOrigin': False, 'maxRange': weapon_range * 2}
  811. elif weapon_type == 'explosive':
  812. base_config['trajectory']['type'] = 'arc'
  813. base_config['hitEffects'] = [{'type': 'explosion', 'priority': 1, 'damage': damage + 20, 'radius': 100, 'delay': 0.1}]
  814. base_config['lifecycle']['type'] = 'ground_impact'
  815. base_config['visual']['hitEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
  816. base_config['visual']['explosionEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
  817. return base_config
  818. def _generate_visual_config(self, weapon_id, weapon_name):
  819. """生成视觉配置"""
  820. # 根据武器ID生成图片编号
  821. weapon_sprite_map = {
  822. 'pea_shooter': '001-1',
  823. 'sharp_carrot': '002',
  824. 'saw_grass': '003',
  825. 'watermelon_bomb': '007',
  826. 'boomerang_plant': '004',
  827. 'hot_pepper': '005',
  828. 'cactus_shotgun': '008',
  829. 'okra_missile': '006',
  830. 'mace_club': '009'
  831. }
  832. sprite_id = weapon_sprite_map.get(weapon_id, '001')
  833. return {
  834. 'weaponSprites': f'images/PlantsSprite/{sprite_id}',
  835. 'fireSound': f'audio/{weapon_id}_shot'
  836. }
  837. def backup_json_config(self):
  838. """备份现有JSON配置"""
  839. try:
  840. if self.json_file.exists():
  841. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  842. backup_file = self.json_file.parent / f"{self.json_file.stem}_backup_{timestamp}.json"
  843. with open(self.json_file, 'r', encoding='utf-8') as src:
  844. with open(backup_file, 'w', encoding='utf-8') as dst:
  845. dst.write(src.read())
  846. print(f"配置已备份到: {backup_file}")
  847. return backup_file
  848. else:
  849. print("JSON文件不存在,无需备份")
  850. return None
  851. except Exception as e:
  852. print(f"备份配置失败: {e}")
  853. return None
  854. def save_json_config(self, config):
  855. """保存配置到JSON文件"""
  856. try:
  857. # 确保目录存在
  858. self.json_file.parent.mkdir(parents=True, exist_ok=True)
  859. with open(self.json_file, 'w', encoding='utf-8') as f:
  860. json.dump(config, f, ensure_ascii=False, indent=2)
  861. print(f"配置已保存到: {self.json_file}")
  862. return True
  863. except Exception as e:
  864. print(f"保存JSON文件失败: {e}")
  865. return False
  866. def import_weapon_config(self):
  867. """导入武器配置的主方法"""
  868. try:
  869. print("开始导入武器配置...")
  870. # 1. 加载现有JSON配置
  871. existing_config = self.load_existing_json_config()
  872. # 2. 读取Excel配置
  873. excel_sheets = self.read_excel_config()
  874. # 3. 解析Excel数据
  875. excel_config = self.parse_weapon_multi_sheet_data(excel_sheets)
  876. # 4. 合并配置
  877. merged_config = self.merge_weapon_configs(existing_config, excel_config)
  878. # 5. 备份现有配置
  879. self.backup_json_config()
  880. # 6. 保存新配置
  881. if self.save_json_config(merged_config):
  882. print("武器配置导入成功!")
  883. return True
  884. else:
  885. print("武器配置保存失败!")
  886. return False
  887. except Exception as e:
  888. print(f"导入武器配置失败: {e}")
  889. return False
  890. def sync_json_to_excel(self):
  891. """将JSON配置同步到Excel文件"""
  892. try:
  893. print("开始将JSON配置同步到Excel文件...")
  894. # 导入生成器模块
  895. from generate_excel_from_json import WeaponExcelGenerator
  896. # 创建Excel生成器
  897. generator = WeaponExcelGenerator(
  898. json_file_path=str(self.json_file),
  899. excel_output_path=str(self.excel_file)
  900. )
  901. # 生成Excel文件
  902. success = generator.generate_excel_file()
  903. if success:
  904. print("✓ JSON配置已成功同步到Excel文件")
  905. return True
  906. else:
  907. print("✗ JSON配置同步到Excel文件失败")
  908. return False
  909. except Exception as e:
  910. print(f"同步JSON到Excel失败: {e}")
  911. return False
  912. def sync_excel_to_json(self):
  913. """将Excel配置同步到JSON文件"""
  914. try:
  915. print("开始将Excel配置同步到JSON文件...")
  916. # 使用现有的导入方法
  917. success = self.import_weapon_config()
  918. if success:
  919. print("✓ Excel配置已成功同步到JSON文件")
  920. return True
  921. else:
  922. print("✗ Excel配置同步到JSON文件失败")
  923. return False
  924. except Exception as e:
  925. print(f"同步Excel到JSON失败: {e}")
  926. return False
  927. def show_sync_menu(self):
  928. """显示同步菜单"""
  929. while True:
  930. print("\n武器配置同步工具")
  931. print("=" * 50)
  932. print("1. 从JSON同步到Excel (推荐)")
  933. print("2. 从Excel同步到JSON")
  934. print("3. 查看文件状态")
  935. print("4. 退出")
  936. print("=" * 50)
  937. choice = input("请选择操作 (1-4): ").strip()
  938. if choice == '1':
  939. print("\n正在从JSON同步到Excel...")
  940. success = self.sync_json_to_excel()
  941. if success:
  942. print("🎉 同步完成!Excel文件已更新")
  943. else:
  944. print("❌ 同步失败!")
  945. elif choice == '2':
  946. print("\n正在从Excel同步到JSON...")
  947. success = self.sync_excel_to_json()
  948. if success:
  949. print("🎉 同步完成!JSON文件已更新")
  950. else:
  951. print("❌ 同步失败!")
  952. elif choice == '3':
  953. self.show_file_status()
  954. elif choice == '4':
  955. print("\n再见!")
  956. break
  957. else:
  958. print("\n❌ 无效选择,请重新输入")
  959. def show_file_status(self):
  960. """显示文件状态"""
  961. print("\n文件状态信息")
  962. print("-" * 30)
  963. # JSON文件状态
  964. if self.json_file.exists():
  965. json_mtime = datetime.fromtimestamp(self.json_file.stat().st_mtime)
  966. print(f"✓ JSON文件: {self.json_file}")
  967. print(f" 最后修改: {json_mtime.strftime('%Y-%m-%d %H:%M:%S')}")
  968. try:
  969. with open(self.json_file, 'r', encoding='utf-8') as f:
  970. config = json.load(f)
  971. weapon_count = len(config.get('weapons', []))
  972. print(f" 武器数量: {weapon_count}")
  973. except Exception as e:
  974. print(f" 读取失败: {e}")
  975. else:
  976. print(f"❌ JSON文件不存在: {self.json_file}")
  977. print()
  978. # Excel文件状态
  979. if self.excel_file.exists():
  980. excel_mtime = datetime.fromtimestamp(self.excel_file.stat().st_mtime)
  981. print(f"✓ Excel文件: {self.excel_file}")
  982. print(f" 最后修改: {excel_mtime.strftime('%Y-%m-%d %H:%M:%S')}")
  983. try:
  984. if PANDAS_AVAILABLE:
  985. sheets = pd.read_excel(self.excel_file, sheet_name=None)
  986. print(f" 工作表数量: {len(sheets)}")
  987. print(f" 工作表名称: {list(sheets.keys())}")
  988. else:
  989. print(" 无法读取详细信息 (pandas未安装)")
  990. except Exception as e:
  991. print(f" 读取失败: {e}")
  992. else:
  993. print(f"❌ Excel文件不存在: {self.excel_file}")
  994. def main():
  995. """主函数"""
  996. print("武器配置管理器")
  997. print("=" * 50)
  998. # 创建武器配置管理器
  999. manager = WeaponConfigManager()
  1000. # 检查命令行参数
  1001. import sys
  1002. if len(sys.argv) > 1:
  1003. if sys.argv[1] == '--sync-excel-to-json':
  1004. print("自动执行:从Excel同步到JSON...")
  1005. success = manager.sync_excel_to_json()
  1006. if success:
  1007. print("🎉 同步完成!JSON文件已更新")
  1008. else:
  1009. print("❌ 同步失败!")
  1010. return
  1011. elif sys.argv[1] == '--sync-json-to-excel':
  1012. print("自动执行:从JSON同步到Excel...")
  1013. success = manager.sync_json_to_excel()
  1014. if success:
  1015. print("🎉 同步完成!Excel文件已更新")
  1016. else:
  1017. print("❌ 同步失败!")
  1018. return
  1019. # 显示同步菜单
  1020. manager.show_sync_menu()
  1021. if __name__ == "__main__":
  1022. main()