weapon_config_manager.py 42 KB

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