enemy_config_manager.py 21 KB

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