weapon_config_manager.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  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. return weapons_config
  146. except Exception as e:
  147. print(f"解析武器配置失败: {e}")
  148. return {'weapons': []}
  149. def parse_config_data(self, df):
  150. """解析配置数据"""
  151. try:
  152. items = []
  153. # 检查第一行是否为表头
  154. first_row = df.iloc[0] if len(df) > 0 else None
  155. is_header = False
  156. if first_row is not None:
  157. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  158. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  159. is_header = True
  160. print(f"检测到表头行,第一列内容: {first_cell}")
  161. for index, row in df.iterrows():
  162. if is_header and index == 0: # 跳过表头
  163. continue
  164. # 转换行数据为字典
  165. item = {}
  166. for col_index, value in enumerate(row):
  167. if col_index < len(df.columns):
  168. col_name = df.columns[col_index]
  169. if pd.notna(value) and str(value).strip():
  170. # 根据映射转换数据类型
  171. param_type = self.weapon_mapping['param_types'].get(col_name, str)
  172. try:
  173. if param_type == int:
  174. item[col_name] = int(float(value))
  175. elif param_type == float:
  176. item[col_name] = float(value)
  177. else:
  178. item[col_name] = str(value).strip()
  179. except (ValueError, TypeError):
  180. item[col_name] = str(value).strip()
  181. # 检查是否有有效的武器ID
  182. weapon_id = item.get('ID') or item.get('id') or item.get('武器ID')
  183. if weapon_id and str(weapon_id).strip():
  184. items.append(item)
  185. return {'items': items}
  186. except Exception as e:
  187. print(f"解析配置数据失败: {e}")
  188. return {'items': []}
  189. def _parse_upgrade_cost_data(self, upgrade_cost_sheet, weapons_list):
  190. """解析升级费用配置数据"""
  191. try:
  192. print(f"开始处理升级费用配置,工作表行数: {len(upgrade_cost_sheet)}")
  193. upgrade_cost_data = []
  194. # 检查第一行是否为表头
  195. first_row = upgrade_cost_sheet.iloc[0] if len(upgrade_cost_sheet) > 0 else None
  196. is_header = False
  197. if first_row is not None:
  198. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  199. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  200. is_header = True
  201. print(f"检测到表头行,第一列内容: {first_cell}")
  202. for index, row in upgrade_cost_sheet.iterrows():
  203. if is_header and index == 0: # 跳过表头
  204. continue
  205. # 支持多种武器ID字段名
  206. weapon_id = None
  207. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  208. if id_field in row and pd.notna(row[id_field]):
  209. weapon_id = row[id_field]
  210. break
  211. if weapon_id is None:
  212. weapon_id = row.iloc[0] if len(row) > 0 else None
  213. if weapon_id and str(weapon_id).strip():
  214. upgrade_levels = {}
  215. # 从第5列开始是等级1-10的费用,从第15列开始是等级1-10的伤害
  216. for level in range(1, 11):
  217. cost_col_index = 4 + (level - 1)
  218. damage_col_index = 14 + (level - 1)
  219. level_config = {}
  220. # 处理费用
  221. if cost_col_index < len(row):
  222. cost = row.iloc[cost_col_index]
  223. if cost and str(cost).strip() and str(cost) != 'nan':
  224. try:
  225. level_config['cost'] = int(float(cost))
  226. except (ValueError, TypeError):
  227. pass
  228. # 处理伤害
  229. if damage_col_index < len(row):
  230. damage = row.iloc[damage_col_index]
  231. if damage and str(damage).strip() and str(damage) != 'nan':
  232. try:
  233. level_config['damage'] = int(float(damage))
  234. except (ValueError, TypeError):
  235. pass
  236. if level_config:
  237. upgrade_levels[str(level)] = level_config
  238. if upgrade_levels:
  239. upgrade_cost_data.append({
  240. 'weapon_id': str(weapon_id).strip(),
  241. 'levels': upgrade_levels
  242. })
  243. # 将升级费用配置合并到武器数据中
  244. for weapon in weapons_list:
  245. weapon_id = weapon.get('ID', '') or weapon.get('id', '')
  246. if weapon_id:
  247. matching_upgrade = None
  248. for upgrade_data in upgrade_cost_data:
  249. if upgrade_data['weapon_id'] == weapon_id:
  250. matching_upgrade = upgrade_data
  251. break
  252. if matching_upgrade:
  253. weapon['upgradeConfig'] = {
  254. 'maxLevel': 10,
  255. 'levels': matching_upgrade['levels']
  256. }
  257. print(f"✓ 为武器 {weapon_id} 添加了升级费用配置")
  258. except Exception as e:
  259. print(f"解析升级费用配置失败: {e}")
  260. def _parse_cost_config_data(self, cost_sheet, weapons_list):
  261. """解析游戏内成本配置数据"""
  262. try:
  263. print(f"开始处理游戏内成本配置,工作表行数: {len(cost_sheet)}")
  264. # 检查第一行是否为表头
  265. first_row = cost_sheet.iloc[0] if len(cost_sheet) > 0 else None
  266. is_header = False
  267. if first_row is not None:
  268. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  269. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  270. is_header = True
  271. for index, row in cost_sheet.iterrows():
  272. if is_header and index == 0: # 跳过表头
  273. continue
  274. # 获取武器ID
  275. weapon_id = None
  276. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  277. if id_field in row and pd.notna(row[id_field]):
  278. weapon_id = str(row[id_field]).strip()
  279. break
  280. if weapon_id is None:
  281. weapon_id = str(row.iloc[0]).strip() if len(row) > 0 else None
  282. if weapon_id:
  283. # 查找对应的武器并添加成本配置
  284. for weapon in weapons_list:
  285. w_id = weapon.get('ID', '') or weapon.get('id', '')
  286. if w_id == weapon_id:
  287. # 构建成本配置
  288. base_cost = 5 # 默认基础成本
  289. shape_costs = {}
  290. # 读取基础成本
  291. for field in ['武器基础售价', 'baseCost', '基础成本']:
  292. if field in row and pd.notna(row[field]):
  293. try:
  294. base_cost = int(float(row[field]))
  295. break
  296. except (ValueError, TypeError):
  297. pass
  298. # 读取各形状成本
  299. shape_fields = {
  300. 'I': ['I形状', 'I_shape', 'I'],
  301. 'H-I': ['H-I形状', 'HI_shape', 'H-I'],
  302. 'L': ['L形状', 'L_shape', 'L'],
  303. 'S': ['S形状', 'S_shape', 'S'],
  304. 'D-T': ['D-T形状', 'DT_shape', 'D-T']
  305. }
  306. for shape_key, field_names in shape_fields.items():
  307. for field_name in field_names:
  308. if field_name in row and pd.notna(row[field_name]):
  309. try:
  310. shape_costs[shape_key] = int(float(row[field_name]))
  311. break
  312. except (ValueError, TypeError):
  313. pass
  314. weapon['inGameCostConfig'] = {
  315. 'baseCost': base_cost,
  316. 'shapeCosts': shape_costs
  317. }
  318. print(f"✓ 为武器 {weapon_id} 添加了游戏内成本配置")
  319. break
  320. except Exception as e:
  321. print(f"解析游戏内成本配置失败: {e}")
  322. def _parse_rarity_weights_data(self, rarity_sheet):
  323. """解析稀有度权重配置数据"""
  324. try:
  325. rarity_weights = {}
  326. # 检查第一行是否为表头
  327. first_row = rarity_sheet.iloc[0] if len(rarity_sheet) > 0 else None
  328. is_header = False
  329. if first_row is not None:
  330. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  331. if first_cell in ['稀有度', 'rarity', 'Rarity']:
  332. is_header = True
  333. for index, row in rarity_sheet.iterrows():
  334. if is_header and index == 0: # 跳过表头
  335. continue
  336. # 获取稀有度和权重
  337. rarity = None
  338. weight = None
  339. for field in ['稀有度', 'rarity', 'Rarity']:
  340. if field in row and pd.notna(row[field]):
  341. rarity = str(row[field]).strip()
  342. break
  343. if rarity is None:
  344. rarity = str(row.iloc[0]).strip() if len(row) > 0 else None
  345. for field in ['权重', 'weight', 'Weight']:
  346. if field in row and pd.notna(row[field]):
  347. try:
  348. weight = int(float(row[field]))
  349. break
  350. except (ValueError, TypeError):
  351. pass
  352. if weight is None and len(row) > 1:
  353. try:
  354. weight = int(float(row.iloc[1]))
  355. except (ValueError, TypeError):
  356. pass
  357. if rarity and weight is not None:
  358. rarity_weights[rarity] = weight
  359. print(f"✓ 添加稀有度权重: {rarity} = {weight}")
  360. return rarity_weights
  361. except Exception as e:
  362. print(f"解析稀有度权重配置失败: {e}")
  363. return {}
  364. def merge_weapon_configs(self, existing_config, excel_config):
  365. """合并现有JSON配置和Excel配置"""
  366. try:
  367. print("开始合并武器配置...")
  368. # 创建现有武器的映射表(按ID索引)
  369. existing_weapons_map = {}
  370. for weapon in existing_config.get('weapons', []):
  371. weapon_id = weapon.get('id')
  372. if weapon_id:
  373. existing_weapons_map[weapon_id] = weapon
  374. print(f"现有武器数量: {len(existing_weapons_map)}")
  375. print(f"Excel武器数量: {len(excel_config.get('weapons', []))}")
  376. # 处理Excel中的武器数据
  377. merged_weapons = []
  378. for excel_weapon in excel_config.get('weapons', []):
  379. weapon_id = excel_weapon.get('ID') or excel_weapon.get('id')
  380. if not weapon_id:
  381. continue
  382. # 转换Excel数据为标准格式
  383. converted_weapon = self._convert_weapon_data(
  384. excel_weapon,
  385. existing_weapons_map.get(weapon_id)
  386. )
  387. if converted_weapon:
  388. merged_weapons.append(converted_weapon)
  389. print(f"✓ 处理武器: {weapon_id}")
  390. # 添加Excel中没有但现有配置中存在的武器
  391. excel_weapon_ids = {w.get('ID') or w.get('id') for w in excel_config.get('weapons', [])}
  392. for weapon_id, existing_weapon in existing_weapons_map.items():
  393. if weapon_id not in excel_weapon_ids:
  394. merged_weapons.append(existing_weapon)
  395. print(f"✓ 保留现有武器: {weapon_id}")
  396. # 构建最终配置
  397. merged_config = existing_config.copy()
  398. merged_config['weapons'] = merged_weapons
  399. # 合并稀有度权重
  400. if 'rarityWeights' in excel_config:
  401. merged_config['rarityWeights'] = excel_config['rarityWeights']
  402. print("✓ 更新稀有度权重配置")
  403. print(f"合并完成,最终武器数量: {len(merged_weapons)}")
  404. return merged_config
  405. except Exception as e:
  406. print(f"合并武器配置失败: {e}")
  407. return existing_config
  408. def _convert_weapon_data(self, item, existing_weapon=None):
  409. """转换武器数据格式"""
  410. try:
  411. # 支持中英文字段名
  412. weapon_id = item.get('id', item.get('ID', ''))
  413. weapon_name = item.get('name', item.get('名称', ''))
  414. if not weapon_id:
  415. print(f"跳过无效武器数据: 缺少武器ID - {item}")
  416. return None
  417. # 获取基础属性
  418. damage = item.get('damage', item.get('伤害', 10))
  419. fire_rate = item.get('fireRate', item.get('射速', 1.0))
  420. weapon_range = item.get('range', item.get('射程', 100))
  421. bullet_speed = item.get('bulletSpeed', item.get('子弹速度', 100))
  422. weapon_type = item.get('type', item.get('类型', ''))
  423. rarity = item.get('rarity', item.get('稀有度', ''))
  424. weight = item.get('weight', item.get('权重', 1))
  425. # 推断武器类型和稀有度(如果为空)
  426. if not weapon_type:
  427. weapon_type = self._infer_weapon_type(weapon_id)
  428. if not rarity:
  429. rarity = self._infer_rarity(damage)
  430. # 根据稀有度设置权重
  431. if weight == 1:
  432. rarity_weights = {'common': 30, 'uncommon': 20, 'rare': 15, 'epic': 8}
  433. weight = rarity_weights.get(rarity, 20)
  434. # 构建基础武器配置
  435. result = {
  436. 'id': weapon_id,
  437. 'name': weapon_name,
  438. 'type': weapon_type,
  439. 'rarity': rarity,
  440. 'weight': weight,
  441. 'stats': {
  442. 'damage': damage,
  443. 'fireRate': fire_rate,
  444. 'range': weapon_range,
  445. 'bulletSpeed': min(bullet_speed, 50) # 限制子弹速度
  446. }
  447. }
  448. # 如果有现有武器配置,保留其bulletConfig和visualConfig
  449. if existing_weapon:
  450. if 'bulletConfig' in existing_weapon:
  451. result['bulletConfig'] = existing_weapon['bulletConfig']
  452. print(f"为武器 {weapon_id} 保留现有的bulletConfig")
  453. else:
  454. result['bulletConfig'] = self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range)
  455. if 'visualConfig' in existing_weapon:
  456. result['visualConfig'] = existing_weapon['visualConfig']
  457. print(f"为武器 {weapon_id} 保留现有的visualConfig")
  458. else:
  459. result['visualConfig'] = self._generate_visual_config(weapon_id, weapon_name)
  460. else:
  461. # 生成默认配置
  462. result['bulletConfig'] = self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range)
  463. result['visualConfig'] = self._generate_visual_config(weapon_id, weapon_name)
  464. # 添加升级配置(如果Excel中有)
  465. if 'upgradeConfig' in item:
  466. result['upgradeConfig'] = item['upgradeConfig']
  467. print(f"为武器 {weapon_id} 添加升级配置")
  468. # 添加游戏内成本配置(如果Excel中有)
  469. if 'inGameCostConfig' in item:
  470. result['inGameCostConfig'] = item['inGameCostConfig']
  471. print(f"为武器 {weapon_id} 添加游戏内成本配置")
  472. return result
  473. except Exception as e:
  474. print(f"转换武器数据失败: {e} - 数据: {item}")
  475. return None
  476. def _infer_weapon_type(self, weapon_id):
  477. """根据武器ID推断武器类型"""
  478. if 'shotgun' in weapon_id or 'cactus' in weapon_id:
  479. return 'shotgun'
  480. elif 'bomb' in weapon_id or 'pepper' in weapon_id:
  481. return 'explosive'
  482. elif 'missile' in weapon_id:
  483. return 'homing_missile'
  484. elif 'boomerang' in weapon_id:
  485. return 'boomerang'
  486. elif 'saw' in weapon_id:
  487. return 'ricochet_piercing'
  488. elif 'carrot' in weapon_id:
  489. return 'piercing'
  490. else:
  491. return 'single_shot'
  492. def _infer_rarity(self, damage):
  493. """根据伤害推断稀有度"""
  494. if damage >= 60:
  495. return 'epic'
  496. elif damage >= 40:
  497. return 'rare'
  498. elif damage >= 25:
  499. return 'uncommon'
  500. else:
  501. return 'common'
  502. def _generate_bullet_config(self, weapon_id, weapon_type, damage, weapon_range):
  503. """生成子弹配置"""
  504. # 基础配置模板
  505. base_config = {
  506. 'count': {'type': 'single', 'amount': 1, 'spreadAngle': 0, 'burstCount': 1, 'burstDelay': 0},
  507. 'trajectory': {'type': 'straight', 'speed': 200, 'gravity': 0, 'arcHeight': 0, 'homingStrength': 0, 'homingDelay': 0},
  508. 'hitEffects': [{'type': 'normal_damage', 'priority': 1, 'damage': damage}],
  509. 'lifecycle': {'type': 'hit_destroy', 'maxLifetime': 5.0, 'penetration': 1, 'ricochetCount': 0, 'returnToOrigin': False},
  510. 'visual': {
  511. 'bulletImages': f'images/PlantsSprite/{sprite_id}',
  512. 'hitEffect': 'Animation/WeaponTx/tx0002/tx0002',
  513. 'trailEffect': True
  514. }
  515. }
  516. # 根据武器类型调整配置
  517. if weapon_type == 'shotgun':
  518. base_config['count'] = {'type': 'spread', 'amount': 5, 'spreadAngle': 30, 'burstCount': 1, 'burstDelay': 0}
  519. base_config['lifecycle']['type'] = 'range_limit'
  520. base_config['lifecycle']['maxRange'] = weapon_range * 2
  521. elif weapon_type == 'piercing':
  522. base_config['hitEffects'] = [{'type': 'pierce_damage', 'priority': 1, 'damage': damage, 'pierceCount': 999}]
  523. base_config['lifecycle'] = {'type': 'range_limit', 'maxLifetime': 5.0, 'penetration': 999, 'ricochetCount': 0, 'returnToOrigin': False, 'maxRange': weapon_range * 2}
  524. elif weapon_type == 'explosive':
  525. base_config['trajectory']['type'] = 'arc'
  526. base_config['hitEffects'] = [{'type': 'explosion', 'priority': 1, 'damage': damage + 20, 'radius': 100, 'delay': 0.1}]
  527. base_config['lifecycle']['type'] = 'ground_impact'
  528. base_config['visual']['hitEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
  529. base_config['visual']['explosionEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
  530. return base_config
  531. def _generate_visual_config(self, weapon_id, weapon_name):
  532. """生成视觉配置"""
  533. # 根据武器ID生成图片编号
  534. weapon_sprite_map = {
  535. 'pea_shooter': '001-1',
  536. 'sharp_carrot': '002',
  537. 'saw_grass': '003',
  538. 'watermelon_bomb': '007',
  539. 'boomerang_plant': '004',
  540. 'hot_pepper': '005',
  541. 'cactus_shotgun': '008',
  542. 'okra_missile': '006',
  543. 'mace_club': '009'
  544. }
  545. sprite_id = weapon_sprite_map.get(weapon_id, '001')
  546. return {
  547. 'weaponSprites': f'images/PlantsSprite/{sprite_id}',
  548. 'fireSound': f'audio/{weapon_id}_shot'
  549. }
  550. def backup_json_config(self):
  551. """备份现有JSON配置"""
  552. try:
  553. if self.json_file.exists():
  554. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  555. backup_file = self.json_file.parent / f"{self.json_file.stem}_backup_{timestamp}.json"
  556. with open(self.json_file, 'r', encoding='utf-8') as src:
  557. with open(backup_file, 'w', encoding='utf-8') as dst:
  558. dst.write(src.read())
  559. print(f"配置已备份到: {backup_file}")
  560. return backup_file
  561. else:
  562. print("JSON文件不存在,无需备份")
  563. return None
  564. except Exception as e:
  565. print(f"备份配置失败: {e}")
  566. return None
  567. def save_json_config(self, config):
  568. """保存配置到JSON文件"""
  569. try:
  570. # 确保目录存在
  571. self.json_file.parent.mkdir(parents=True, exist_ok=True)
  572. with open(self.json_file, 'w', encoding='utf-8') as f:
  573. json.dump(config, f, ensure_ascii=False, indent=2)
  574. print(f"配置已保存到: {self.json_file}")
  575. return True
  576. except Exception as e:
  577. print(f"保存JSON文件失败: {e}")
  578. return False
  579. def import_weapon_config(self):
  580. """导入武器配置的主方法"""
  581. try:
  582. print("开始导入武器配置...")
  583. # 1. 加载现有JSON配置
  584. existing_config = self.load_existing_json_config()
  585. # 2. 读取Excel配置
  586. excel_sheets = self.read_excel_config()
  587. # 3. 解析Excel数据
  588. excel_config = self.parse_weapon_multi_sheet_data(excel_sheets)
  589. # 4. 合并配置
  590. merged_config = self.merge_weapon_configs(existing_config, excel_config)
  591. # 5. 备份现有配置
  592. self.backup_json_config()
  593. # 6. 保存新配置
  594. if self.save_json_config(merged_config):
  595. print("武器配置导入成功!")
  596. return True
  597. else:
  598. print("武器配置保存失败!")
  599. return False
  600. except Exception as e:
  601. print(f"导入武器配置失败: {e}")
  602. return False
  603. def main():
  604. """主函数"""
  605. print("武器配置管理器")
  606. print("=" * 50)
  607. # 创建武器配置管理器
  608. manager = WeaponConfigManager()
  609. # 导入配置
  610. success = manager.import_weapon_config()
  611. if success:
  612. print("\n✓ 武器配置导入完成")
  613. else:
  614. print("\n✗ 武器配置导入失败")
  615. if __name__ == "__main__":
  616. main()