enemy_config_manager.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 敌人配置管理器
  5. 负责从Excel文件读取敌人配置并与现有JSON配置合并
  6. 只修改策划配置的值,保持JSON中其他字段不变
  7. 作者: AI Assistant
  8. 日期: 2024
  9. """
  10. import json
  11. import os
  12. from pathlib import Path
  13. from datetime import datetime
  14. import copy
  15. try:
  16. import pandas as pd
  17. PANDAS_AVAILABLE = True
  18. except ImportError:
  19. PANDAS_AVAILABLE = False
  20. print("警告: pandas未安装,敌人配置管理器将不可用")
  21. class EnemyConfigManager:
  22. """敌人配置管理器类"""
  23. def __init__(self, excel_path=None, json_path=None):
  24. """初始化敌人配置管理器"""
  25. if not PANDAS_AVAILABLE:
  26. raise ImportError("pandas未安装,无法使用敌人配置管理器")
  27. # 设置文件路径
  28. self.script_dir = Path(__file__).parent
  29. self.excel_path = Path(excel_path) if excel_path else self.script_dir / "敌人配置表.xlsx"
  30. self.json_path = Path(json_path) if json_path else self.script_dir.parent / "enemies.json"
  31. # 存储配置数据
  32. self.excel_data = {}
  33. self.existing_json = []
  34. self.merged_config = []
  35. print(f"敌人配置管理器初始化完成")
  36. print(f"Excel文件: {self.excel_path}")
  37. print(f"JSON文件: {self.json_path}")
  38. def load_existing_json(self):
  39. """加载现有的JSON配置"""
  40. if self.json_path.exists():
  41. try:
  42. with open(self.json_path, 'r', encoding='utf-8') as f:
  43. self.existing_json = json.load(f)
  44. print(f"成功加载现有JSON配置,包含 {len(self.existing_json)} 个敌人")
  45. return True
  46. except Exception as e:
  47. print(f"加载JSON配置失败: {e}")
  48. self.existing_json = []
  49. return False
  50. else:
  51. print("JSON文件不存在,将创建新配置")
  52. self.existing_json = []
  53. return True
  54. def read_excel_data(self):
  55. """从Excel文件读取敌人配置数据"""
  56. if not self.excel_path.exists():
  57. raise FileNotFoundError(f"Excel文件不存在: {self.excel_path}")
  58. try:
  59. # 读取所有工作表,不指定header以保留所有行
  60. excel_data = pd.read_excel(self.excel_path, sheet_name=None, header=None)
  61. print(f"找到工作表: {list(excel_data.keys())}")
  62. # 解析各个工作表
  63. self.excel_data = {
  64. 'basic': self._parse_basic_config(excel_data.get('敌人基础配置')),
  65. 'combat': self._parse_combat_config(excel_data.get('战斗配置')),
  66. 'movement': self._parse_movement_config(excel_data.get('移动配置')),
  67. 'visual': self._parse_visual_config(excel_data.get('视觉配置')),
  68. 'audio': self._parse_audio_config(excel_data.get('音频配置')),
  69. 'special': self._parse_special_abilities(excel_data.get('特殊能力配置')),
  70. 'boss': self._parse_boss_config(excel_data.get('BOSS配置'))
  71. }
  72. print("Excel数据解析完成")
  73. return True
  74. except Exception as e:
  75. print(f"读取Excel文件失败: {e}")
  76. return False
  77. def _parse_basic_config(self, df):
  78. """解析敌人基础配置"""
  79. if df is None:
  80. return {}
  81. config = {}
  82. # 从第1行开始处理(跳过第0行标题)
  83. for i in range(1, len(df)):
  84. row = df.iloc[i]
  85. enemy_id = str(row.iloc[0]).strip()
  86. if pd.isna(row.iloc[0]) or enemy_id == '':
  87. continue
  88. config[enemy_id] = {
  89. 'name': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '',
  90. 'type': str(row.iloc[2]) if not pd.isna(row.iloc[2]) else '',
  91. 'health': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 100,
  92. 'maxHealth': int(row.iloc[4]) if not pd.isna(row.iloc[4]) else 100,
  93. 'defense': int(row.iloc[5]) if not pd.isna(row.iloc[5]) else 0,
  94. 'speed': float(row.iloc[6]) if not pd.isna(row.iloc[6]) else 30.0,
  95. 'dropEnergy': int(row.iloc[7]) if not pd.isna(row.iloc[7]) else 1
  96. }
  97. print(f"解析基础配置: {len(config)} 个敌人")
  98. return config
  99. def _parse_combat_config(self, df):
  100. """解析战斗配置"""
  101. if df is None:
  102. return {}
  103. config = {}
  104. for index, row in df.iterrows():
  105. if index == 0: # 跳过标题行
  106. continue
  107. enemy_id = str(row.iloc[0]).strip()
  108. if pd.isna(row.iloc[0]) or enemy_id == '':
  109. continue
  110. config[enemy_id] = {
  111. 'attackDamage': int(row.iloc[1]) if not pd.isna(row.iloc[1]) else 1,
  112. 'attackRange': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1.0,
  113. 'attackSpeed': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 1.0,
  114. 'canBlock': bool(row.iloc[4]) if not pd.isna(row.iloc[4]) else False,
  115. 'blockChance': float(row.iloc[5]) if not pd.isna(row.iloc[5]) else 0.0,
  116. 'blockDamageReduction': float(row.iloc[6]) if not pd.isna(row.iloc[6]) else 0.5,
  117. 'attackCooldown': float(row.iloc[7]) if not pd.isna(row.iloc[7]) else 1.0,
  118. 'attackType': str(row.iloc[8]) if not pd.isna(row.iloc[8]) else 'melee',
  119. 'attackDelay': float(row.iloc[9]) if not pd.isna(row.iloc[9]) else 1.0,
  120. 'weaponType': str(row.iloc[10]) if not pd.isna(row.iloc[10]) else 'none',
  121. 'projectileType': str(row.iloc[11]) if not pd.isna(row.iloc[11]) else 'none',
  122. 'projectileSpeed': float(row.iloc[12]) if not pd.isna(row.iloc[12]) else 100.0
  123. }
  124. print(f"解析战斗配置: {len(config)} 个敌人")
  125. return config
  126. def _parse_movement_config(self, df):
  127. """解析移动配置"""
  128. if df is None:
  129. return {}
  130. config = {}
  131. for index, row in df.iterrows():
  132. if index == 0: # 跳过标题行
  133. continue
  134. enemy_id = str(row.iloc[0]).strip()
  135. if pd.isna(row.iloc[0]) or enemy_id == '':
  136. continue
  137. config[enemy_id] = {
  138. 'pattern': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else 'direct',
  139. 'speed': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 30.0,
  140. 'patrolRange': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 100,
  141. 'chaseRange': int(row.iloc[4]) if not pd.isna(row.iloc[4]) else 200,
  142. 'rotationSpeed': float(row.iloc[5]) if not pd.isna(row.iloc[5]) else 180.0,
  143. 'moveType': str(row.iloc[6]) if not pd.isna(row.iloc[6]) else 'straight',
  144. 'swingAmplitude': float(row.iloc[7]) if not pd.isna(row.iloc[7]) else 0.0,
  145. 'swingFrequency': float(row.iloc[8]) if not pd.isna(row.iloc[8]) else 0.0,
  146. 'speedVariation': float(row.iloc[9]) if not pd.isna(row.iloc[9]) else 0.1
  147. }
  148. print(f"解析移动配置: {len(config)} 个敌人")
  149. return config
  150. def _parse_visual_config(self, df):
  151. """解析视觉配置"""
  152. if df is None:
  153. return {}
  154. visual_config = {}
  155. for index, row in df.iterrows():
  156. if index == 0: # 跳过标题行
  157. continue
  158. enemy_id = str(row.iloc[0]).strip()
  159. if pd.isna(row.iloc[0]) or enemy_id == '':
  160. continue
  161. config = {
  162. 'sprite_path': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '',
  163. 'scale': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1.0,
  164. 'animation_speed': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 1.0,
  165. 'flip_horizontal': bool(row.iloc[4]) if not pd.isna(row.iloc[4]) else False,
  166. 'animations': {
  167. 'idle': str(row.iloc[5]) if not pd.isna(row.iloc[5]) else 'idle',
  168. 'walk': str(row.iloc[6]) if not pd.isna(row.iloc[6]) else 'walk',
  169. 'attack': str(row.iloc[7]) if not pd.isna(row.iloc[7]) else 'attack',
  170. 'death': str(row.iloc[8]) if not pd.isna(row.iloc[8]) else 'dead'
  171. }
  172. }
  173. # 武器道具(可选)
  174. if len(row) > 9 and not pd.isna(row.iloc[9]):
  175. weapon_prop = str(row.iloc[9]).strip()
  176. if weapon_prop:
  177. config['weapon_prop'] = weapon_prop
  178. visual_config[enemy_id] = config
  179. print(f"解析视觉配置: {len(visual_config)} 个敌人")
  180. return visual_config
  181. def _parse_audio_config(self, df):
  182. """解析音频配置"""
  183. if df is None:
  184. return {}
  185. audio_config = {}
  186. for index, row in df.iterrows():
  187. if index == 0: # 跳过标题行
  188. continue
  189. enemy_id = str(row.iloc[0]).strip()
  190. if pd.isna(row.iloc[0]) or enemy_id == '':
  191. continue
  192. config = {
  193. 'attack_sound': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '',
  194. 'death_sound': str(row.iloc[2]) if not pd.isna(row.iloc[2]) else '',
  195. 'hit_sound': str(row.iloc[3]) if not pd.isna(row.iloc[3]) else '',
  196. 'walk_sound': str(row.iloc[4]) if not pd.isna(row.iloc[4]) else ''
  197. }
  198. audio_config[enemy_id] = config
  199. print(f"解析音频配置: {len(audio_config)} 个敌人")
  200. return audio_config
  201. def _parse_special_abilities(self, df):
  202. """解析特殊能力配置"""
  203. if df is None:
  204. return {}
  205. abilities_config = {}
  206. for index, row in df.iterrows():
  207. if index == 0: # 跳过标题行
  208. continue
  209. enemy_id = str(row.iloc[0]).strip()
  210. if pd.isna(row.iloc[0]) or enemy_id == '':
  211. continue
  212. ability_type = str(row.iloc[1]).strip() if not pd.isna(row.iloc[1]) else ''
  213. if not ability_type:
  214. # 没有特殊能力的敌人
  215. if enemy_id not in abilities_config:
  216. abilities_config[enemy_id] = []
  217. continue
  218. ability = {
  219. 'type': ability_type,
  220. 'damage': int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 0,
  221. 'range': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 0,
  222. 'cooldown': int(row.iloc[4]) if not pd.isna(row.iloc[4]) else 0
  223. }
  224. if enemy_id not in abilities_config:
  225. abilities_config[enemy_id] = []
  226. abilities_config[enemy_id].append(ability)
  227. print(f"解析特殊能力配置: {len(abilities_config)} 个敌人")
  228. return abilities_config
  229. def _parse_boss_config(self, df):
  230. """解析BOSS配置"""
  231. if df is None:
  232. return {}
  233. boss_config = {}
  234. for index, row in df.iterrows():
  235. if index == 0: # 跳过标题行
  236. continue
  237. enemy_id = str(row.iloc[0]).strip()
  238. if pd.isna(row.iloc[0]) or enemy_id == '':
  239. continue
  240. config = {
  241. 'is_boss': bool(row.iloc[1]) if not pd.isna(row.iloc[1]) else False,
  242. 'phases': int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1,
  243. 'rage_threshold': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 0.3,
  244. 'rage_damage_multiplier': float(row.iloc[4]) if not pd.isna(row.iloc[4]) else 1.0,
  245. 'rage_speed_multiplier': float(row.iloc[5]) if not pd.isna(row.iloc[5]) else 1.0
  246. }
  247. boss_config[enemy_id] = config
  248. print(f"解析BOSS配置: {len(boss_config)} 个敌人")
  249. return boss_config
  250. def merge_configurations(self):
  251. """合并Excel配置和现有JSON配置"""
  252. print("开始合并配置...")
  253. # 创建敌人ID到JSON索引的映射
  254. json_enemy_map = {}
  255. for i, enemy in enumerate(self.existing_json):
  256. json_enemy_map[enemy.get('id', '')] = i
  257. # 获取所有Excel中的敌人ID
  258. excel_enemy_ids = set()
  259. for config_type in ['basic', 'combat', 'movement', 'visual', 'audio', 'special', 'boss']:
  260. if config_type in self.excel_data:
  261. excel_enemy_ids.update(self.excel_data[config_type].keys())
  262. # 复制现有JSON作为基础
  263. self.merged_config = copy.deepcopy(self.existing_json)
  264. # 处理每个敌人
  265. for enemy_id in excel_enemy_ids:
  266. if enemy_id in json_enemy_map:
  267. # 更新现有敌人
  268. json_index = json_enemy_map[enemy_id]
  269. self._update_enemy_config(self.merged_config[json_index], enemy_id)
  270. print(f"更新敌人配置: {enemy_id}")
  271. else:
  272. # 创建新敌人(如果需要)
  273. new_enemy = self._create_new_enemy_config(enemy_id)
  274. if new_enemy:
  275. self.merged_config.append(new_enemy)
  276. print(f"创建新敌人配置: {enemy_id}")
  277. print(f"配置合并完成,共 {len(self.merged_config)} 个敌人")
  278. def _update_enemy_config(self, enemy_config, enemy_id):
  279. """更新单个敌人的配置"""
  280. # 更新基础配置
  281. if 'basic' in self.excel_data and enemy_id in self.excel_data['basic']:
  282. basic_data = self.excel_data['basic'][enemy_id]
  283. if 'name' in basic_data:
  284. enemy_config['name'] = basic_data['name']
  285. if 'type' in basic_data:
  286. enemy_config['type'] = basic_data['type']
  287. # 更新stats部分
  288. if 'stats' not in enemy_config:
  289. enemy_config['stats'] = {}
  290. stats_mapping = {
  291. 'health': 'health',
  292. 'maxHealth': 'maxHealth',
  293. 'defense': 'defense',
  294. 'speed': 'speed',
  295. 'dropEnergy': 'dropEnergy'
  296. }
  297. for excel_key, json_key in stats_mapping.items():
  298. if excel_key in basic_data:
  299. enemy_config['stats'][json_key] = basic_data[excel_key]
  300. # 更新战斗配置
  301. if 'combat' in self.excel_data and enemy_id in self.excel_data['combat']:
  302. combat_data = self.excel_data['combat'][enemy_id]
  303. if 'combat' not in enemy_config:
  304. enemy_config['combat'] = {}
  305. combat_mapping = {
  306. 'attackDamage': 'attackDamage',
  307. 'attackRange': 'attackRange',
  308. 'attackSpeed': 'attackSpeed',
  309. 'canBlock': 'canBlock',
  310. 'blockChance': 'blockChance',
  311. 'blockDamageReduction': 'blockDamageReduction',
  312. 'attackCooldown': 'attackCooldown',
  313. 'attackType': 'attackType',
  314. 'attackDelay': 'attackDelay',
  315. 'weaponType': 'weaponType',
  316. 'projectileType': 'projectileType',
  317. 'projectileSpeed': 'projectileSpeed'
  318. }
  319. for excel_key, json_key in combat_mapping.items():
  320. if excel_key in combat_data:
  321. enemy_config['combat'][json_key] = combat_data[excel_key]
  322. # 更新移动配置
  323. if 'movement' in self.excel_data and enemy_id in self.excel_data['movement']:
  324. movement_data = self.excel_data['movement'][enemy_id]
  325. if 'movement' not in enemy_config:
  326. enemy_config['movement'] = {}
  327. movement_mapping = {
  328. 'pattern': 'pattern',
  329. 'speed': 'speed',
  330. 'patrolRange': 'patrolRange',
  331. 'chaseRange': 'chaseRange',
  332. 'rotationSpeed': 'rotationSpeed',
  333. 'moveType': 'moveType',
  334. 'swingAmplitude': 'swingAmplitude',
  335. 'swingFrequency': 'swingFrequency',
  336. 'speedVariation': 'speedVariation'
  337. }
  338. for excel_key, json_key in movement_mapping.items():
  339. if excel_key in movement_data:
  340. enemy_config['movement'][json_key] = movement_data[excel_key]
  341. # 更新视觉配置
  342. if 'visual' in self.excel_data and enemy_id in self.excel_data['visual']:
  343. visual_data = self.excel_data['visual'][enemy_id]
  344. if 'visual' not in enemy_config:
  345. enemy_config['visual'] = {}
  346. # 直接映射视觉配置
  347. for key, value in visual_data.items():
  348. enemy_config['visual'][key] = value
  349. # 更新音频配置
  350. if 'audio' in self.excel_data and enemy_id in self.excel_data['audio']:
  351. audio_data = self.excel_data['audio'][enemy_id]
  352. if 'audio' not in enemy_config:
  353. enemy_config['audio'] = {}
  354. # 直接映射音频配置
  355. for key, value in audio_data.items():
  356. enemy_config['audio'][key] = value
  357. # 更新特殊能力配置
  358. if 'special' in self.excel_data and enemy_id in self.excel_data['special']:
  359. abilities_data = self.excel_data['special'][enemy_id]
  360. if 'special_abilities' not in enemy_config:
  361. enemy_config['special_abilities'] = []
  362. # 替换特殊能力列表
  363. enemy_config['special_abilities'] = abilities_data
  364. # 更新BOSS配置
  365. if 'boss' in self.excel_data and enemy_id in self.excel_data['boss']:
  366. boss_data = self.excel_data['boss'][enemy_id]
  367. if 'boss' not in enemy_config:
  368. enemy_config['boss'] = {}
  369. # 直接映射BOSS配置
  370. for key, value in boss_data.items():
  371. enemy_config['boss'][key] = value
  372. def _create_new_enemy_config(self, enemy_id):
  373. """创建新的敌人配置(基于Excel数据)"""
  374. # 这里可以根据需要实现创建新敌人的逻辑
  375. # 暂时返回None,只更新现有敌人
  376. return None
  377. def backup_json(self):
  378. """备份原始JSON文件"""
  379. if not self.json_path.exists():
  380. return True
  381. try:
  382. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  383. backup_path = self.json_path.parent / f"{self.json_path.stem}_backup_{timestamp}.json"
  384. with open(self.json_path, 'r', encoding='utf-8') as src:
  385. with open(backup_path, 'w', encoding='utf-8') as dst:
  386. dst.write(src.read())
  387. print(f"备份文件已创建: {backup_path}")
  388. return True
  389. except Exception as e:
  390. print(f"备份失败: {e}")
  391. return False
  392. def save_merged_config(self):
  393. """保存合并后的配置到JSON文件"""
  394. try:
  395. # 确保目录存在
  396. self.json_path.parent.mkdir(parents=True, exist_ok=True)
  397. with open(self.json_path, 'w', encoding='utf-8') as f:
  398. json.dump(self.merged_config, f, ensure_ascii=False, indent=2)
  399. print(f"配置已保存到: {self.json_path}")
  400. return True
  401. except Exception as e:
  402. print(f"保存配置失败: {e}")
  403. return False
  404. def import_config(self):
  405. """执行完整的配置导入流程"""
  406. print("=== 敌人配置导入开始 ===")
  407. try:
  408. # 1. 加载现有JSON配置
  409. if not self.load_existing_json():
  410. print("加载现有JSON配置失败")
  411. return False
  412. # 2. 读取Excel数据
  413. if not self.read_excel_data():
  414. print("读取Excel数据失败")
  415. return False
  416. # 3. 合并配置
  417. self.merge_configurations()
  418. # 4. 备份原文件
  419. if not self.backup_json():
  420. print("备份失败,但继续执行")
  421. # 5. 保存新配置
  422. if not self.save_merged_config():
  423. print("保存配置失败")
  424. return False
  425. print("=== 敌人配置导入完成 ===")
  426. return True
  427. except Exception as e:
  428. print(f"配置导入过程中发生错误: {e}")
  429. return False
  430. # 测试函数
  431. def test_enemy_config_manager():
  432. """测试敌人配置管理器"""
  433. try:
  434. manager = EnemyConfigManager()
  435. success = manager.import_config()
  436. if success:
  437. print("敌人配置导入测试成功")
  438. else:
  439. print("敌人配置导入测试失败")
  440. return success
  441. except Exception as e:
  442. print(f"测试失败: {e}")
  443. return False
  444. if __name__ == "__main__":
  445. test_enemy_config_manager()