weapon_config_manager.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  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. # 方块价格配置字段
  51. '基础每格成本': int,
  52. 'I形状成本': int,
  53. 'H-I形状成本': int,
  54. 'L形状成本': int,
  55. 'S形状成本': int,
  56. 'D-T形状成本': int,
  57. # 英文字段支持
  58. 'id': str,
  59. 'name': str,
  60. 'type': str,
  61. 'weight': int,
  62. 'damage': int,
  63. 'fireRate': float,
  64. 'range': int,
  65. 'bulletSpeed': int,
  66. 'baseCost': int,
  67. 'I_shape_cost': int,
  68. 'HI_shape_cost': int,
  69. 'L_shape_cost': int,
  70. 'S_shape_cost': int,
  71. 'DT_shape_cost': int
  72. }
  73. }
  74. def load_existing_json_config(self):
  75. """加载现有的JSON配置文件"""
  76. try:
  77. if self.json_file.exists():
  78. with open(self.json_file, 'r', encoding='utf-8') as f:
  79. config = json.load(f)
  80. print(f"成功加载现有JSON配置,包含 {len(config.get('weapons', []))} 个武器")
  81. return config
  82. else:
  83. print(f"JSON文件不存在,将创建新配置: {self.json_file}")
  84. return {'weapons': [], 'blockSizes': []}
  85. except Exception as e:
  86. print(f"加载JSON配置失败: {e}")
  87. return {'weapons': [], 'blockSizes': []}
  88. def read_excel_config(self):
  89. """读取Excel配置文件"""
  90. if not PANDAS_AVAILABLE:
  91. raise Exception("pandas未安装,无法读取Excel文件")
  92. if not self.excel_file.exists():
  93. raise Exception(f"Excel文件不存在: {self.excel_file}")
  94. try:
  95. # 读取所有工作表
  96. all_sheets = pd.read_excel(self.excel_file, sheet_name=None)
  97. print(f"成功读取Excel文件,包含工作表: {list(all_sheets.keys())}")
  98. return all_sheets
  99. except Exception as e:
  100. raise Exception(f"读取Excel文件失败: {e}")
  101. def parse_weapon_multi_sheet_data(self, all_sheets_data):
  102. """解析武器配置表的多工作表数据"""
  103. weapons_config = {'weapons': []}
  104. try:
  105. # 解析武器基础配置工作表
  106. base_sheet = None
  107. for sheet_name in ['武器基础配置', 'Weapon Config', 'weapons', '武器配置']:
  108. if sheet_name in all_sheets_data:
  109. base_sheet = all_sheets_data[sheet_name]
  110. break
  111. if base_sheet is not None:
  112. base_config = self.parse_config_data(base_sheet)
  113. if 'items' in base_config:
  114. weapons_config['weapons'] = base_config['items']
  115. print(f"成功解析武器基础配置,共{len(base_config['items'])}个武器")
  116. # 解析武器升级费用配置工作表
  117. upgrade_cost_sheet = None
  118. for sheet_name in ['武器升级费用配置', 'Weapon Upgrade Cost', 'upgrade_costs', '升级费用']:
  119. if sheet_name in all_sheets_data:
  120. upgrade_cost_sheet = all_sheets_data[sheet_name]
  121. print(f"找到升级费用配置工作表: {sheet_name}")
  122. break
  123. if upgrade_cost_sheet is not None:
  124. self._parse_upgrade_cost_data(upgrade_cost_sheet, weapons_config['weapons'])
  125. # 解析游戏内成本配置工作表
  126. cost_sheet = None
  127. for sheet_name in ['游戏内成本配置', 'In Game Cost', 'cost_config', '成本配置']:
  128. if sheet_name in all_sheets_data:
  129. cost_sheet = all_sheets_data[sheet_name]
  130. print(f"找到游戏内成本配置工作表: {sheet_name}")
  131. break
  132. if cost_sheet is not None:
  133. self._parse_cost_config_data(cost_sheet, weapons_config['weapons'])
  134. # 解析方块形状配置工作表
  135. block_shape_sheet = None
  136. for sheet_name in ['方块形状配置', 'Block Shape Config', 'block_shapes', '形状配置']:
  137. if sheet_name in all_sheets_data:
  138. block_shape_sheet = all_sheets_data[sheet_name]
  139. print(f"找到方块形状配置工作表: {sheet_name}")
  140. break
  141. if block_shape_sheet is not None:
  142. weapons_config['blockSizes'] = self._parse_block_shape_data(block_shape_sheet)
  143. return weapons_config
  144. except Exception as e:
  145. print(f"解析武器配置失败: {e}")
  146. return {'weapons': []}
  147. def parse_config_data(self, df):
  148. """解析配置数据"""
  149. try:
  150. items = []
  151. # 检查第一行是否为表头
  152. first_row = df.iloc[0] if len(df) > 0 else None
  153. is_header = False
  154. if first_row is not None:
  155. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  156. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  157. is_header = True
  158. print(f"检测到表头行,第一列内容: {first_cell}")
  159. for index, row in df.iterrows():
  160. if is_header and index == 0: # 跳过表头
  161. continue
  162. # 转换行数据为字典
  163. item = {}
  164. for col_index, value in enumerate(row):
  165. if col_index < len(df.columns):
  166. col_name = df.columns[col_index]
  167. if pd.notna(value) and str(value).strip():
  168. # 根据映射转换数据类型
  169. param_type = self.weapon_mapping['param_types'].get(col_name, str)
  170. try:
  171. if param_type == int:
  172. item[col_name] = int(float(value))
  173. elif param_type == float:
  174. item[col_name] = float(value)
  175. else:
  176. item[col_name] = str(value).strip()
  177. except (ValueError, TypeError):
  178. item[col_name] = str(value).strip()
  179. # 检查是否有有效的武器ID
  180. weapon_id = item.get('ID') or item.get('id') or item.get('武器ID')
  181. if weapon_id and str(weapon_id).strip():
  182. items.append(item)
  183. return {'items': items}
  184. except Exception as e:
  185. print(f"解析配置数据失败: {e}")
  186. return {'items': []}
  187. def _parse_upgrade_cost_data(self, upgrade_cost_sheet, weapons_list):
  188. """解析升级费用配置数据"""
  189. try:
  190. print(f"开始处理升级费用配置,工作表行数: {len(upgrade_cost_sheet)}")
  191. upgrade_cost_data = []
  192. # 检查第一行是否为表头
  193. first_row = upgrade_cost_sheet.iloc[0] if len(upgrade_cost_sheet) > 0 else None
  194. is_header = False
  195. if first_row is not None:
  196. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  197. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  198. is_header = True
  199. print(f"检测到表头行,第一列内容: {first_cell}")
  200. for index, row in upgrade_cost_sheet.iterrows():
  201. if is_header and index == 0: # 跳过表头
  202. continue
  203. # 支持多种武器ID字段名
  204. weapon_id = None
  205. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  206. if id_field in row and pd.notna(row[id_field]):
  207. weapon_id = row[id_field]
  208. break
  209. if weapon_id is None:
  210. weapon_id = row.iloc[0] if len(row) > 0 else None
  211. if weapon_id and str(weapon_id).strip():
  212. upgrade_levels = {}
  213. # 从第5列开始是等级1-10的费用,从第15列开始是等级1-10的伤害
  214. for level in range(1, 11):
  215. cost_col_index = 4 + (level - 1)
  216. damage_col_index = 14 + (level - 1)
  217. level_config = {}
  218. # 处理费用
  219. if cost_col_index < len(row):
  220. cost = row.iloc[cost_col_index]
  221. if cost and str(cost).strip() and str(cost) != 'nan':
  222. try:
  223. level_config['cost'] = int(float(cost))
  224. except (ValueError, TypeError):
  225. pass
  226. # 处理伤害
  227. if damage_col_index < len(row):
  228. damage = row.iloc[damage_col_index]
  229. if damage and str(damage).strip() and str(damage) != 'nan':
  230. try:
  231. level_config['damage'] = int(float(damage))
  232. except (ValueError, TypeError):
  233. pass
  234. if level_config:
  235. upgrade_levels[str(level)] = level_config
  236. if upgrade_levels:
  237. upgrade_cost_data.append({
  238. 'weapon_id': str(weapon_id).strip(),
  239. 'levels': upgrade_levels
  240. })
  241. # 将升级费用配置合并到武器数据中
  242. for weapon in weapons_list:
  243. weapon_id = weapon.get('ID', '') or weapon.get('id', '')
  244. if weapon_id:
  245. matching_upgrade = None
  246. for upgrade_data in upgrade_cost_data:
  247. if upgrade_data['weapon_id'] == weapon_id:
  248. matching_upgrade = upgrade_data
  249. break
  250. if matching_upgrade:
  251. weapon['upgradeConfig'] = {
  252. 'maxLevel': 10,
  253. 'levels': matching_upgrade['levels']
  254. }
  255. print(f"✓ 为武器 {weapon_id} 添加了升级费用配置")
  256. except Exception as e:
  257. print(f"解析升级费用配置失败: {e}")
  258. def _parse_cost_config_data(self, cost_sheet, weapons_list):
  259. """解析游戏内成本配置数据"""
  260. try:
  261. print(f"开始处理游戏内成本配置,工作表行数: {len(cost_sheet)}")
  262. # 检查第一行是否为表头
  263. first_row = cost_sheet.iloc[0] if len(cost_sheet) > 0 else None
  264. is_header = False
  265. if first_row is not None:
  266. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  267. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  268. is_header = True
  269. for index, row in cost_sheet.iterrows():
  270. if is_header and index == 0: # 跳过表头
  271. continue
  272. # 获取武器ID
  273. weapon_id = None
  274. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  275. if id_field in row and pd.notna(row[id_field]):
  276. weapon_id = str(row[id_field]).strip()
  277. break
  278. if weapon_id is None:
  279. weapon_id = str(row.iloc[0]).strip() if len(row) > 0 else None
  280. if weapon_id:
  281. # 查找对应的武器并添加成本配置
  282. for weapon in weapons_list:
  283. w_id = weapon.get('ID', '') or weapon.get('id', '')
  284. if w_id == weapon_id:
  285. # 构建成本配置
  286. base_cost = 5 # 默认基础成本
  287. shape_costs = {}
  288. # 读取基础成本
  289. for field in ['武器基础售价', 'baseCost', '基础成本']:
  290. if field in row and pd.notna(row[field]):
  291. try:
  292. base_cost = int(float(row[field]))
  293. break
  294. except (ValueError, TypeError):
  295. pass
  296. # 读取各形状成本
  297. shape_fields = {
  298. 'I': ['I形状成本', 'I形状', 'I_shape', 'I'],
  299. 'H-I': ['H-I形状成本', 'H-I形状', 'HI_shape', 'H-I'],
  300. 'L': ['L形状成本', 'L形状', 'L_shape', 'L'],
  301. 'S': ['S形状成本', 'S形状', 'S_shape', 'S'],
  302. 'D-T': ['D-T形状成本', 'D-T形状', 'DT_shape', 'D-T'],
  303. 'L2': ['L2形状成本', 'L2形状', 'L2_shape', 'L2'],
  304. 'L3': ['L3形状成本', 'L3形状', 'L3_shape', 'L3'],
  305. 'L4': ['L4形状成本', 'L4形状', 'L4_shape', 'L4'],
  306. 'F-S': ['F-S形状成本', 'F-S形状', 'FS_shape', 'F-S'],
  307. 'T': ['T形状成本', 'T形状', 'T_shape', 'T']
  308. }
  309. for shape_key, field_names in shape_fields.items():
  310. for field_name in field_names:
  311. if field_name in row and pd.notna(row[field_name]):
  312. try:
  313. shape_costs[shape_key] = int(float(row[field_name]))
  314. break
  315. except (ValueError, TypeError):
  316. pass
  317. weapon['inGameCostConfig'] = {
  318. 'baseCost': base_cost,
  319. 'shapeCosts': shape_costs
  320. }
  321. print(f"✓ 为武器 {weapon_id} 添加了游戏内成本配置")
  322. break
  323. except Exception as e:
  324. print(f"解析游戏内成本配置失败: {e}")
  325. def _parse_block_shape_data(self, block_shape_sheet):
  326. """解析方块形状配置数据"""
  327. try:
  328. block_shapes = []
  329. # 检查第一行是否为表头
  330. first_row = block_shape_sheet.iloc[0] if len(block_shape_sheet) > 0 else None
  331. is_header = False
  332. if first_row is not None:
  333. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  334. if first_cell in ['ID', 'id', '形状ID', 'shape_id']:
  335. is_header = True
  336. print(f"检测到方块形状配置表头行,第一列内容: {first_cell}")
  337. for index, row in block_shape_sheet.iterrows():
  338. if is_header and index == 0: # 跳过表头
  339. continue
  340. # 获取方块形状数据
  341. shape_id = None
  342. shape_name = None
  343. shape_matrix = None
  344. grid_count = None
  345. cost_multiplier = None
  346. description = None
  347. # 获取ID
  348. for field in ['ID', 'id', '形状ID', 'shape_id']:
  349. if field in row and pd.notna(row[field]):
  350. shape_id = str(row[field]).strip()
  351. break
  352. if shape_id is None:
  353. shape_id = str(row.iloc[0]).strip() if len(row) > 0 else None
  354. # 获取名称
  355. for field in ['名称', 'name', 'Name', '形状名称']:
  356. if field in row and pd.notna(row[field]):
  357. shape_name = str(row[field]).strip()
  358. break
  359. if shape_name is None and len(row) > 1:
  360. shape_name = str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else None
  361. # 获取形状矩阵
  362. for field in ['形状矩阵', 'shape', 'matrix', '矩阵']:
  363. if field in row and pd.notna(row[field]):
  364. shape_matrix = str(row[field]).strip()
  365. break
  366. if shape_matrix is None and len(row) > 2:
  367. shape_matrix = str(row.iloc[2]).strip() if pd.notna(row.iloc[2]) else None
  368. # 获取占用格数
  369. for field in ['占用格数', 'gridCount', 'grid_count', '格数']:
  370. if field in row and pd.notna(row[field]):
  371. try:
  372. grid_count = int(float(row[field]))
  373. break
  374. except (ValueError, TypeError):
  375. pass
  376. if grid_count is None and len(row) > 3:
  377. try:
  378. grid_count = int(float(row.iloc[3])) if pd.notna(row.iloc[3]) else None
  379. except (ValueError, TypeError):
  380. pass
  381. # 获取成本倍数
  382. for field in ['成本倍数', 'costMultiplier', 'cost_multiplier', '倍数']:
  383. if field in row and pd.notna(row[field]):
  384. try:
  385. cost_multiplier = int(float(row[field]))
  386. break
  387. except (ValueError, TypeError):
  388. pass
  389. if cost_multiplier is None and len(row) > 4:
  390. try:
  391. cost_multiplier = int(float(row.iloc[4])) if pd.notna(row.iloc[4]) else None
  392. except (ValueError, TypeError):
  393. pass
  394. # 获取描述
  395. for field in ['描述', 'description', 'Description', '说明']:
  396. if field in row and pd.notna(row[field]):
  397. description = str(row[field]).strip()
  398. break
  399. if description is None and len(row) > 5:
  400. description = str(row.iloc[5]).strip() if pd.notna(row.iloc[5]) else None
  401. # 如果有有效的形状ID,则创建形状配置
  402. if shape_id:
  403. # 解析形状矩阵
  404. shape_array = self._parse_shape_matrix(shape_matrix)
  405. block_shape = {
  406. "id": shape_id,
  407. "name": shape_name or shape_id,
  408. "shape": shape_array,
  409. "gridCount": grid_count or len([cell for row in shape_array for cell in row if cell == 1]),
  410. "costMultiplier": cost_multiplier or grid_count or 1,
  411. "description": description or f"{shape_name or shape_id}形状"
  412. }
  413. block_shapes.append(block_shape)
  414. print(f"✓ 添加方块形状配置: {shape_id} ({shape_name})")
  415. return block_shapes
  416. except Exception as e:
  417. print(f"解析方块形状配置失败: {e}")
  418. return []
  419. def _parse_shape_matrix(self, shape_matrix_str):
  420. """解析形状矩阵字符串为二维数组"""
  421. try:
  422. if not shape_matrix_str:
  423. return [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
  424. shape_matrix_str = str(shape_matrix_str).strip()
  425. # 尝试解析JSON格式的矩阵字符串,如 "[0, 1, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0]"
  426. if '[' in shape_matrix_str and ']' in shape_matrix_str:
  427. try:
  428. # 添加外层方括号使其成为有效的JSON数组
  429. json_str = '[' + shape_matrix_str + ']'
  430. import json
  431. shape_array = json.loads(json_str)
  432. # 确保是4x4矩阵
  433. while len(shape_array) < 4:
  434. shape_array.append([0, 0, 0, 0])
  435. for i in range(len(shape_array)):
  436. if len(shape_array[i]) < 4:
  437. shape_array[i].extend([0] * (4 - len(shape_array[i])))
  438. shape_array[i] = shape_array[i][:4]
  439. return shape_array[:4]
  440. except (json.JSONDecodeError, ValueError) as e:
  441. print(f"JSON解析失败: {e}, 尝试其他解析方式")
  442. # 按换行符分割行(原有逻辑保留作为备用)
  443. lines = shape_matrix_str.split('\n')
  444. shape_array = []
  445. for line in lines:
  446. line = line.strip()
  447. if line:
  448. # 将每个字符转换为数字
  449. row = [int(char) for char in line if char in '01']
  450. # 确保每行有4个元素
  451. while len(row) < 4:
  452. row.append(0)
  453. shape_array.append(row[:4]) # 只取前4个元素
  454. # 确保有4行
  455. while len(shape_array) < 4:
  456. shape_array.append([0, 0, 0, 0])
  457. return shape_array[:4] # 只取前4行
  458. except Exception as e:
  459. print(f"解析形状矩阵失败: {e}, 使用默认矩阵")
  460. return [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
  461. def merge_weapon_configs(self, existing_config, excel_config):
  462. """合并现有JSON配置和Excel配置"""
  463. try:
  464. print("开始合并武器配置...")
  465. # 创建现有武器的映射表(按ID索引)
  466. existing_weapons_map = {}
  467. for weapon in existing_config.get('weapons', []):
  468. weapon_id = weapon.get('id')
  469. if weapon_id:
  470. existing_weapons_map[weapon_id] = weapon
  471. print(f"现有武器数量: {len(existing_weapons_map)}")
  472. print(f"Excel武器数量: {len(excel_config.get('weapons', []))}")
  473. # 处理Excel中的武器数据
  474. merged_weapons = []
  475. for excel_weapon in excel_config.get('weapons', []):
  476. weapon_id = excel_weapon.get('ID') or excel_weapon.get('id')
  477. if not weapon_id:
  478. continue
  479. # 转换Excel数据为标准格式
  480. converted_weapon = self._convert_weapon_data(
  481. excel_weapon,
  482. existing_weapons_map.get(weapon_id)
  483. )
  484. if converted_weapon:
  485. merged_weapons.append(converted_weapon)
  486. print(f"✓ 处理武器: {weapon_id}")
  487. # 添加Excel中没有但现有配置中存在的武器
  488. excel_weapon_ids = {w.get('ID') or w.get('id') for w in excel_config.get('weapons', [])}
  489. for weapon_id, existing_weapon in existing_weapons_map.items():
  490. if weapon_id not in excel_weapon_ids:
  491. merged_weapons.append(existing_weapon)
  492. print(f"✓ 保留现有武器: {weapon_id}")
  493. # 构建最终配置
  494. merged_config = existing_config.copy()
  495. merged_config['weapons'] = merged_weapons
  496. # 合并方块形状配置
  497. if 'blockSizes' in excel_config:
  498. merged_config['blockSizes'] = excel_config['blockSizes']
  499. print(f"✓ 更新方块形状配置,共{len(excel_config['blockSizes'])}个形状")
  500. print(f"合并完成,最终武器数量: {len(merged_weapons)}")
  501. return merged_config
  502. except Exception as e:
  503. print(f"合并武器配置失败: {e}")
  504. return existing_config
  505. def _convert_weapon_data(self, item, existing_weapon=None):
  506. """转换武器数据格式"""
  507. try:
  508. # 支持中英文字段名
  509. weapon_id = item.get('id', item.get('ID', ''))
  510. weapon_name = item.get('name', item.get('名称', ''))
  511. if not weapon_id:
  512. print(f"跳过无效武器数据: 缺少武器ID - {item}")
  513. return None
  514. # 获取基础属性
  515. damage = item.get('damage', item.get('伤害', 10))
  516. fire_rate = item.get('fireRate', item.get('射速', 1.0))
  517. weapon_range = item.get('range', item.get('射程', 100))
  518. bullet_speed = item.get('bulletSpeed', item.get('子弹速度', 100))
  519. weapon_type = item.get('type', item.get('类型', ''))
  520. weight = item.get('weight', item.get('权重', 1))
  521. # 推断武器类型(如果为空)
  522. if not weapon_type:
  523. weapon_type = self._infer_weapon_type(weapon_id)
  524. # 设置默认权重(如果为空)
  525. if weight == 1:
  526. weight = 20 # 默认权重
  527. # 构建基础武器配置
  528. result = {
  529. 'id': weapon_id,
  530. 'name': weapon_name,
  531. 'type': weapon_type,
  532. 'weight': weight,
  533. 'stats': {
  534. 'damage': damage,
  535. 'fireRate': fire_rate,
  536. 'range': weapon_range,
  537. 'bulletSpeed': min(bullet_speed, 50) # 限制子弹速度
  538. }
  539. }
  540. # 如果有现有武器配置,保留其bulletConfig和visualConfig
  541. if existing_weapon:
  542. if 'bulletConfig' in existing_weapon:
  543. result['bulletConfig'] = existing_weapon['bulletConfig']
  544. print(f"为武器 {weapon_id} 保留现有的bulletConfig")
  545. else:
  546. result['bulletConfig'] = self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range)
  547. if 'visualConfig' in existing_weapon:
  548. result['visualConfig'] = existing_weapon['visualConfig']
  549. print(f"为武器 {weapon_id} 保留现有的visualConfig")
  550. else:
  551. result['visualConfig'] = self._generate_visual_config(weapon_id, weapon_name)
  552. else:
  553. # 生成默认配置
  554. result['bulletConfig'] = self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range)
  555. result['visualConfig'] = self._generate_visual_config(weapon_id, weapon_name)
  556. # 添加升级配置(如果Excel中有)
  557. if 'upgradeConfig' in item:
  558. result['upgradeConfig'] = item['upgradeConfig']
  559. print(f"为武器 {weapon_id} 添加升级配置")
  560. # 添加游戏内成本配置(如果Excel中有)
  561. if 'inGameCostConfig' in item:
  562. result['inGameCostConfig'] = item['inGameCostConfig']
  563. print(f"为武器 {weapon_id} 添加游戏内成本配置")
  564. return result
  565. except Exception as e:
  566. print(f"转换武器数据失败: {e} - 数据: {item}")
  567. return None
  568. def _infer_weapon_type(self, weapon_id):
  569. """根据武器ID推断武器类型"""
  570. if 'shotgun' in weapon_id or 'cactus' in weapon_id:
  571. return 'shotgun'
  572. elif 'bomb' in weapon_id or 'pepper' in weapon_id:
  573. return 'explosive'
  574. elif 'missile' in weapon_id:
  575. return 'homing_missile'
  576. elif 'boomerang' in weapon_id:
  577. return 'boomerang'
  578. elif 'saw' in weapon_id:
  579. return 'ricochet_piercing'
  580. elif 'carrot' in weapon_id:
  581. return 'piercing'
  582. else:
  583. return 'single_shot'
  584. def _generate_bullet_config(self, weapon_id, weapon_type, damage, weapon_range):
  585. """生成子弹配置"""
  586. # 基础配置模板
  587. base_config = {
  588. 'count': {'type': 'single', 'amount': 1, 'spreadAngle': 0, 'burstCount': 1, 'burstDelay': 0},
  589. 'trajectory': {'type': 'straight', 'speed': 200, 'gravity': 0, 'arcHeight': 0, 'homingStrength': 0, 'homingDelay': 0},
  590. 'hitEffects': [{'type': 'normal_damage', 'priority': 1, 'damage': damage}],
  591. 'lifecycle': {'type': 'hit_destroy', 'maxLifetime': 5.0, 'penetration': 1, 'ricochetCount': 0, 'returnToOrigin': False},
  592. 'visual': {
  593. 'bulletImages': f'images/PlantsSprite/{sprite_id}',
  594. 'hitEffect': 'Animation/WeaponTx/tx0002/tx0002',
  595. 'trailEffect': True
  596. }
  597. }
  598. # 根据武器类型调整配置
  599. if weapon_type == 'shotgun':
  600. base_config['count'] = {'type': 'spread', 'amount': 5, 'spreadAngle': 30, 'burstCount': 1, 'burstDelay': 0}
  601. base_config['lifecycle']['type'] = 'range_limit'
  602. base_config['lifecycle']['maxRange'] = weapon_range * 2
  603. elif weapon_type == 'piercing':
  604. base_config['hitEffects'] = [{'type': 'pierce_damage', 'priority': 1, 'damage': damage, 'pierceCount': 999}]
  605. base_config['lifecycle'] = {'type': 'range_limit', 'maxLifetime': 5.0, 'penetration': 999, 'ricochetCount': 0, 'returnToOrigin': False, 'maxRange': weapon_range * 2}
  606. elif weapon_type == 'explosive':
  607. base_config['trajectory']['type'] = 'arc'
  608. base_config['hitEffects'] = [{'type': 'explosion', 'priority': 1, 'damage': damage + 20, 'radius': 100, 'delay': 0.1}]
  609. base_config['lifecycle']['type'] = 'ground_impact'
  610. base_config['visual']['hitEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
  611. base_config['visual']['explosionEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
  612. return base_config
  613. def _generate_visual_config(self, weapon_id, weapon_name):
  614. """生成视觉配置"""
  615. # 根据武器ID生成图片编号
  616. weapon_sprite_map = {
  617. 'pea_shooter': '001-1',
  618. 'sharp_carrot': '002',
  619. 'saw_grass': '003',
  620. 'watermelon_bomb': '007',
  621. 'boomerang_plant': '004',
  622. 'hot_pepper': '005',
  623. 'cactus_shotgun': '008',
  624. 'okra_missile': '006',
  625. 'mace_club': '009'
  626. }
  627. sprite_id = weapon_sprite_map.get(weapon_id, '001')
  628. return {
  629. 'weaponSprites': f'images/PlantsSprite/{sprite_id}',
  630. 'fireSound': f'audio/{weapon_id}_shot'
  631. }
  632. def backup_json_config(self):
  633. """备份现有JSON配置"""
  634. try:
  635. if self.json_file.exists():
  636. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  637. backup_file = self.json_file.parent / f"{self.json_file.stem}_backup_{timestamp}.json"
  638. with open(self.json_file, 'r', encoding='utf-8') as src:
  639. with open(backup_file, 'w', encoding='utf-8') as dst:
  640. dst.write(src.read())
  641. print(f"配置已备份到: {backup_file}")
  642. return backup_file
  643. else:
  644. print("JSON文件不存在,无需备份")
  645. return None
  646. except Exception as e:
  647. print(f"备份配置失败: {e}")
  648. return None
  649. def save_json_config(self, config):
  650. """保存配置到JSON文件"""
  651. try:
  652. # 确保目录存在
  653. self.json_file.parent.mkdir(parents=True, exist_ok=True)
  654. with open(self.json_file, 'w', encoding='utf-8') as f:
  655. json.dump(config, f, ensure_ascii=False, indent=2)
  656. print(f"配置已保存到: {self.json_file}")
  657. return True
  658. except Exception as e:
  659. print(f"保存JSON文件失败: {e}")
  660. return False
  661. def import_weapon_config(self):
  662. """导入武器配置的主方法"""
  663. try:
  664. print("开始导入武器配置...")
  665. # 1. 加载现有JSON配置
  666. existing_config = self.load_existing_json_config()
  667. # 2. 读取Excel配置
  668. excel_sheets = self.read_excel_config()
  669. # 3. 解析Excel数据
  670. excel_config = self.parse_weapon_multi_sheet_data(excel_sheets)
  671. # 4. 合并配置
  672. merged_config = self.merge_weapon_configs(existing_config, excel_config)
  673. # 5. 备份现有配置
  674. self.backup_json_config()
  675. # 6. 保存新配置
  676. if self.save_json_config(merged_config):
  677. print("武器配置导入成功!")
  678. return True
  679. else:
  680. print("武器配置保存失败!")
  681. return False
  682. except Exception as e:
  683. print(f"导入武器配置失败: {e}")
  684. return False
  685. def sync_json_to_excel(self):
  686. """将JSON配置同步到Excel文件"""
  687. try:
  688. print("开始将JSON配置同步到Excel文件...")
  689. # 导入生成器模块
  690. from generate_excel_from_json import WeaponExcelGenerator
  691. # 创建Excel生成器
  692. generator = WeaponExcelGenerator(
  693. json_file_path=str(self.json_file),
  694. excel_output_path=str(self.excel_file)
  695. )
  696. # 生成Excel文件
  697. success = generator.generate_excel_file()
  698. if success:
  699. print("✓ JSON配置已成功同步到Excel文件")
  700. return True
  701. else:
  702. print("✗ JSON配置同步到Excel文件失败")
  703. return False
  704. except Exception as e:
  705. print(f"同步JSON到Excel失败: {e}")
  706. return False
  707. def sync_excel_to_json(self):
  708. """将Excel配置同步到JSON文件"""
  709. try:
  710. print("开始将Excel配置同步到JSON文件...")
  711. # 使用现有的导入方法
  712. success = self.import_weapon_config()
  713. if success:
  714. print("✓ Excel配置已成功同步到JSON文件")
  715. return True
  716. else:
  717. print("✗ Excel配置同步到JSON文件失败")
  718. return False
  719. except Exception as e:
  720. print(f"同步Excel到JSON失败: {e}")
  721. return False
  722. def show_sync_menu(self):
  723. """显示同步菜单"""
  724. while True:
  725. print("\n武器配置同步工具")
  726. print("=" * 50)
  727. print("1. 从JSON同步到Excel (推荐)")
  728. print("2. 从Excel同步到JSON")
  729. print("3. 查看文件状态")
  730. print("4. 退出")
  731. print("=" * 50)
  732. choice = input("请选择操作 (1-4): ").strip()
  733. if choice == '1':
  734. print("\n正在从JSON同步到Excel...")
  735. success = self.sync_json_to_excel()
  736. if success:
  737. print("🎉 同步完成!Excel文件已更新")
  738. else:
  739. print("❌ 同步失败!")
  740. elif choice == '2':
  741. print("\n正在从Excel同步到JSON...")
  742. success = self.sync_excel_to_json()
  743. if success:
  744. print("🎉 同步完成!JSON文件已更新")
  745. else:
  746. print("❌ 同步失败!")
  747. elif choice == '3':
  748. self.show_file_status()
  749. elif choice == '4':
  750. print("\n再见!")
  751. break
  752. else:
  753. print("\n❌ 无效选择,请重新输入")
  754. def show_file_status(self):
  755. """显示文件状态"""
  756. print("\n文件状态信息")
  757. print("-" * 30)
  758. # JSON文件状态
  759. if self.json_file.exists():
  760. json_mtime = datetime.fromtimestamp(self.json_file.stat().st_mtime)
  761. print(f"✓ JSON文件: {self.json_file}")
  762. print(f" 最后修改: {json_mtime.strftime('%Y-%m-%d %H:%M:%S')}")
  763. try:
  764. with open(self.json_file, 'r', encoding='utf-8') as f:
  765. config = json.load(f)
  766. weapon_count = len(config.get('weapons', []))
  767. print(f" 武器数量: {weapon_count}")
  768. except Exception as e:
  769. print(f" 读取失败: {e}")
  770. else:
  771. print(f"❌ JSON文件不存在: {self.json_file}")
  772. print()
  773. # Excel文件状态
  774. if self.excel_file.exists():
  775. excel_mtime = datetime.fromtimestamp(self.excel_file.stat().st_mtime)
  776. print(f"✓ Excel文件: {self.excel_file}")
  777. print(f" 最后修改: {excel_mtime.strftime('%Y-%m-%d %H:%M:%S')}")
  778. try:
  779. if PANDAS_AVAILABLE:
  780. sheets = pd.read_excel(self.excel_file, sheet_name=None)
  781. print(f" 工作表数量: {len(sheets)}")
  782. print(f" 工作表名称: {list(sheets.keys())}")
  783. else:
  784. print(" 无法读取详细信息 (pandas未安装)")
  785. except Exception as e:
  786. print(f" 读取失败: {e}")
  787. else:
  788. print(f"❌ Excel文件不存在: {self.excel_file}")
  789. def main():
  790. """主函数"""
  791. print("武器配置管理器")
  792. print("=" * 50)
  793. # 创建武器配置管理器
  794. manager = WeaponConfigManager()
  795. # 显示同步菜单
  796. manager.show_sync_menu()
  797. if __name__ == "__main__":
  798. main()