enemy_config_manager.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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. # 读取所有工作表
  60. excel_data = pd.read_excel(self.excel_path, sheet_name=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. for index, row in df.iterrows():
  83. if index == 0: # 跳过标题行
  84. continue
  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 _, row in df.iterrows():
  156. enemy_id = str(row.get('敌人ID', '')).strip()
  157. if not enemy_id or enemy_id == '敌人ID':
  158. continue
  159. config = {
  160. 'sprite_path': str(row.get('精灵路径', '')).strip(),
  161. 'scale': float(row.get('缩放比例', 1.0)),
  162. 'animation_speed': float(row.get('动画速度', 1.0)),
  163. 'flip_horizontal': bool(row.get('水平翻转', False)),
  164. 'animations': {
  165. 'idle': str(row.get('待机动画', 'idle')).strip(),
  166. 'walk': str(row.get('行走动画', 'walk')).strip(),
  167. 'attack': str(row.get('攻击动画', 'attack')).strip(),
  168. 'death': str(row.get('死亡动画', 'dead')).strip()
  169. }
  170. }
  171. # 武器道具(可选)
  172. weapon_prop = str(row.get('武器道具', '')).strip()
  173. if weapon_prop:
  174. config['weapon_prop'] = weapon_prop
  175. visual_config[enemy_id] = config
  176. return visual_config
  177. def _parse_audio_config(self, df):
  178. """解析音频配置"""
  179. if df is None:
  180. return {}
  181. audio_config = {}
  182. for _, row in df.iterrows():
  183. enemy_id = str(row.get('敌人ID', '')).strip()
  184. if not enemy_id or enemy_id == '敌人ID':
  185. continue
  186. config = {
  187. 'attack_sound': str(row.get('攻击音效', '')).strip(),
  188. 'death_sound': str(row.get('死亡音效', '')).strip(),
  189. 'hit_sound': str(row.get('受击音效', '')).strip(),
  190. 'walk_sound': str(row.get('行走音效', '')).strip()
  191. }
  192. # 可选音效
  193. optional_sounds = {
  194. 'block_sound': '格挡音效',
  195. 'stealth_sound': '隐身音效',
  196. 'armor_break_sound': '护甲破碎音效',
  197. 'fuse_sound': '引信音效'
  198. }
  199. for key, col_name in optional_sounds.items():
  200. sound = str(row.get(col_name, '')).strip()
  201. if sound:
  202. config[key] = sound
  203. audio_config[enemy_id] = config
  204. return audio_config
  205. def _parse_special_abilities(self, df):
  206. """解析特殊能力配置"""
  207. if df is None:
  208. return {}
  209. abilities_config = {}
  210. for _, row in df.iterrows():
  211. enemy_id = str(row.get('敌人ID', '')).strip()
  212. if not enemy_id or enemy_id == '敌人ID':
  213. continue
  214. ability_type = str(row.get('能力类型', '')).strip()
  215. if not ability_type:
  216. # 没有特殊能力的敌人
  217. if enemy_id not in abilities_config:
  218. abilities_config[enemy_id] = []
  219. continue
  220. ability = {
  221. 'type': ability_type,
  222. 'damage': int(row.get('伤害', 0)),
  223. 'range': int(row.get('范围', 0)),
  224. 'cooldown': int(row.get('冷却时间', 0))
  225. }
  226. if enemy_id not in abilities_config:
  227. abilities_config[enemy_id] = []
  228. abilities_config[enemy_id].append(ability)
  229. return abilities_config
  230. def _parse_boss_config(self, df):
  231. """解析BOSS配置"""
  232. if df is None:
  233. return {}
  234. boss_config = {}
  235. for _, row in df.iterrows():
  236. enemy_id = str(row.get('敌人ID', '')).strip()
  237. if not enemy_id or enemy_id == '敌人ID':
  238. continue
  239. config = {
  240. 'is_boss': bool(row.get('是否BOSS', False)),
  241. 'phases': int(row.get('阶段数', 1)),
  242. 'rage_threshold': float(row.get('狂暴阈值', 0.3)),
  243. 'rage_damage_multiplier': float(row.get('狂暴伤害倍数', 1.0)),
  244. 'rage_speed_multiplier': float(row.get('狂暴速度倍数', 1.0))
  245. }
  246. boss_config[enemy_id] = config
  247. return boss_config
  248. def merge_configurations(self):
  249. """合并Excel配置和现有JSON配置"""
  250. print("开始合并配置...")
  251. # 创建敌人ID到JSON索引的映射
  252. json_enemy_map = {}
  253. for i, enemy in enumerate(self.existing_json):
  254. json_enemy_map[enemy.get('id', '')] = i
  255. # 获取所有Excel中的敌人ID
  256. excel_enemy_ids = set()
  257. for config_type in ['basic', 'combat', 'movement', 'visual', 'audio', 'special_abilities', 'boss']:
  258. if config_type in self.excel_data:
  259. excel_enemy_ids.update(self.excel_data[config_type].keys())
  260. # 复制现有JSON作为基础
  261. self.merged_config = copy.deepcopy(self.existing_json)
  262. # 处理每个敌人
  263. for enemy_id in excel_enemy_ids:
  264. if enemy_id in json_enemy_map:
  265. # 更新现有敌人
  266. json_index = json_enemy_map[enemy_id]
  267. self._update_enemy_config(self.merged_config[json_index], enemy_id)
  268. print(f"更新敌人配置: {enemy_id}")
  269. else:
  270. # 创建新敌人(如果需要)
  271. new_enemy = self._create_new_enemy_config(enemy_id)
  272. if new_enemy:
  273. self.merged_config.append(new_enemy)
  274. print(f"创建新敌人配置: {enemy_id}")
  275. print(f"配置合并完成,共 {len(self.merged_config)} 个敌人")
  276. def _update_enemy_config(self, enemy_config, enemy_id):
  277. """更新单个敌人的配置"""
  278. # 更新基础配置
  279. if 'basic' in self.excel_data and enemy_id in self.excel_data['basic']:
  280. basic_data = self.excel_data['basic'][enemy_id]
  281. if 'name' in basic_data:
  282. enemy_config['name'] = basic_data['name']
  283. if 'type' in basic_data:
  284. enemy_config['type'] = basic_data['type']
  285. # 更新stats部分
  286. if 'stats' not in enemy_config:
  287. enemy_config['stats'] = {}
  288. stats_mapping = {
  289. 'health': 'health',
  290. 'maxHealth': 'maxHealth',
  291. 'defense': 'defense',
  292. 'speed': 'speed',
  293. 'dropEnergy': 'dropEnergy'
  294. }
  295. for excel_key, json_key in stats_mapping.items():
  296. if excel_key in basic_data:
  297. enemy_config['stats'][json_key] = basic_data[excel_key]
  298. # 更新战斗配置
  299. if 'combat' in self.excel_data and enemy_id in self.excel_data['combat']:
  300. combat_data = self.excel_data['combat'][enemy_id]
  301. if 'combat' not in enemy_config:
  302. enemy_config['combat'] = {}
  303. combat_mapping = {
  304. 'attackDamage': 'attackDamage',
  305. 'attackRange': 'attackRange',
  306. 'attackSpeed': 'attackSpeed',
  307. 'canBlock': 'canBlock',
  308. 'blockChance': 'blockChance',
  309. 'blockDamageReduction': 'blockDamageReduction',
  310. 'attackCooldown': 'attackCooldown',
  311. 'attackType': 'attackType',
  312. 'attackDelay': 'attackDelay',
  313. 'weaponType': 'weaponType',
  314. 'projectileType': 'projectileType',
  315. 'projectileSpeed': 'projectileSpeed'
  316. }
  317. for excel_key, json_key in combat_mapping.items():
  318. if excel_key in combat_data:
  319. enemy_config['combat'][json_key] = combat_data[excel_key]
  320. # 更新移动配置
  321. if 'movement' in self.excel_data and enemy_id in self.excel_data['movement']:
  322. movement_data = self.excel_data['movement'][enemy_id]
  323. if 'movement' not in enemy_config:
  324. enemy_config['movement'] = {}
  325. movement_mapping = {
  326. 'pattern': 'pattern',
  327. 'speed': 'speed',
  328. 'patrolRange': 'patrolRange',
  329. 'chaseRange': 'chaseRange',
  330. 'rotationSpeed': 'rotationSpeed',
  331. 'moveType': 'moveType',
  332. 'swingAmplitude': 'swingAmplitude',
  333. 'swingFrequency': 'swingFrequency',
  334. 'speedVariation': 'speedVariation'
  335. }
  336. for excel_key, json_key in movement_mapping.items():
  337. if excel_key in movement_data:
  338. enemy_config['movement'][json_key] = movement_data[excel_key]
  339. # 更新视觉配置
  340. if 'visual' in self.excel_data and enemy_id in self.excel_data['visual']:
  341. visual_data = self.excel_data['visual'][enemy_id]
  342. if 'visual' not in enemy_config:
  343. enemy_config['visual'] = {}
  344. # 直接映射视觉配置
  345. for key, value in visual_data.items():
  346. enemy_config['visual'][key] = value
  347. # 更新音频配置
  348. if 'audio' in self.excel_data and enemy_id in self.excel_data['audio']:
  349. audio_data = self.excel_data['audio'][enemy_id]
  350. if 'audio' not in enemy_config:
  351. enemy_config['audio'] = {}
  352. # 直接映射音频配置
  353. for key, value in audio_data.items():
  354. enemy_config['audio'][key] = value
  355. # 更新特殊能力配置
  356. if 'special_abilities' in self.excel_data and enemy_id in self.excel_data['special_abilities']:
  357. abilities_data = self.excel_data['special_abilities'][enemy_id]
  358. if 'special_abilities' not in enemy_config:
  359. enemy_config['special_abilities'] = []
  360. # 替换特殊能力列表
  361. enemy_config['special_abilities'] = abilities_data
  362. # 更新BOSS配置
  363. if 'boss' in self.excel_data and enemy_id in self.excel_data['boss']:
  364. boss_data = self.excel_data['boss'][enemy_id]
  365. if 'boss' not in enemy_config:
  366. enemy_config['boss'] = {}
  367. # 直接映射BOSS配置
  368. for key, value in boss_data.items():
  369. enemy_config['boss'][key] = value
  370. def _create_new_enemy_config(self, enemy_id):
  371. """创建新的敌人配置(基于Excel数据)"""
  372. # 这里可以根据需要实现创建新敌人的逻辑
  373. # 暂时返回None,只更新现有敌人
  374. return None
  375. def backup_json(self):
  376. """备份原始JSON文件"""
  377. if not self.json_path.exists():
  378. return True
  379. try:
  380. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  381. backup_path = self.json_path.parent / f"{self.json_path.stem}_backup_{timestamp}.json"
  382. with open(self.json_path, 'r', encoding='utf-8') as src:
  383. with open(backup_path, 'w', encoding='utf-8') as dst:
  384. dst.write(src.read())
  385. print(f"备份文件已创建: {backup_path}")
  386. return True
  387. except Exception as e:
  388. print(f"备份失败: {e}")
  389. return False
  390. def save_merged_config(self):
  391. """保存合并后的配置到JSON文件"""
  392. try:
  393. # 确保目录存在
  394. self.json_path.parent.mkdir(parents=True, exist_ok=True)
  395. with open(self.json_path, 'w', encoding='utf-8') as f:
  396. json.dump(self.merged_config, f, ensure_ascii=False, indent=2)
  397. print(f"配置已保存到: {self.json_path}")
  398. return True
  399. except Exception as e:
  400. print(f"保存配置失败: {e}")
  401. return False
  402. def import_config(self):
  403. """执行完整的配置导入流程"""
  404. print("=== 敌人配置导入开始 ===")
  405. try:
  406. # 1. 加载现有JSON配置
  407. if not self.load_existing_json():
  408. print("加载现有JSON配置失败")
  409. return False
  410. # 2. 读取Excel数据
  411. if not self.read_excel_data():
  412. print("读取Excel数据失败")
  413. return False
  414. # 3. 合并配置
  415. self.merge_configurations()
  416. # 4. 备份原文件
  417. if not self.backup_json():
  418. print("备份失败,但继续执行")
  419. # 5. 保存新配置
  420. if not self.save_merged_config():
  421. print("保存配置失败")
  422. return False
  423. print("=== 敌人配置导入完成 ===")
  424. return True
  425. except Exception as e:
  426. print(f"配置导入过程中发生错误: {e}")
  427. return False
  428. # 测试函数
  429. def test_enemy_config_manager():
  430. """测试敌人配置管理器"""
  431. try:
  432. manager = EnemyConfigManager()
  433. success = manager.import_config()
  434. if success:
  435. print("敌人配置导入测试成功")
  436. else:
  437. print("敌人配置导入测试失败")
  438. return success
  439. except Exception as e:
  440. print(f"测试失败: {e}")
  441. return False
  442. if __name__ == "__main__":
  443. test_enemy_config_manager()