enemy_config_manager.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  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. # 新增:统一环境变量大小写不敏感控制
  22. CASE_INSENSITIVE = str(os.environ.get('CFG_CASE_INSENSITIVE', '1')).strip().lower() in ('1', 'true', 'yes', 'on')
  23. class EnemyConfigManager:
  24. """敌人配置管理器类"""
  25. def __init__(self, excel_path=None, json_path=None):
  26. """初始化敌人配置管理器"""
  27. if not PANDAS_AVAILABLE:
  28. raise ImportError("pandas未安装,无法使用敌人配置管理器")
  29. # 设置文件路径
  30. self.script_dir = Path(__file__).parent
  31. self.excel_path = Path(excel_path) if excel_path else self.script_dir / "敌人配置表.xlsx"
  32. self.json_path = Path(json_path) if json_path else self.script_dir.parent / "enemies.json"
  33. # 存储配置数据
  34. self.excel_data = {}
  35. self.existing_json = []
  36. self.merged_config = []
  37. print(f"敌人配置管理器初始化完成")
  38. print(f"Excel文件: {self.excel_path}")
  39. print(f"JSON文件: {self.json_path}")
  40. def load_existing_json(self):
  41. """加载现有的JSON配置"""
  42. if self.json_path.exists():
  43. try:
  44. with open(self.json_path, 'r', encoding='utf-8') as f:
  45. self.existing_json = json.load(f)
  46. print(f"成功加载现有JSON配置,包含 {len(self.existing_json)} 个敌人")
  47. return True
  48. except Exception as e:
  49. print(f"加载JSON配置失败: {e}")
  50. self.existing_json = []
  51. return False
  52. else:
  53. print("JSON文件不存在,将创建新配置")
  54. self.existing_json = []
  55. return True
  56. def read_excel_data(self):
  57. """从Excel文件读取敌人配置数据"""
  58. if not self.excel_path.exists():
  59. raise FileNotFoundError(f"Excel文件不存在: {self.excel_path}")
  60. try:
  61. # 读取所有工作表,不指定header以保留所有行
  62. excel_data = pd.read_excel(self.excel_path, sheet_name=None, header=None)
  63. print(f"找到工作表: {list(excel_data.keys())}")
  64. # 解析各个工作表(支持大小写不敏感与英文别名)
  65. self.excel_data = {
  66. 'basic': self._parse_basic_config(self._get_sheet(excel_data, ['敌人基础配置','Enemy Basic Config','basic','enemy_basic'])),
  67. 'combat': self._parse_combat_config(self._get_sheet(excel_data, ['战斗配置','Combat Config','combat'])),
  68. 'movement': self._parse_movement_config(self._get_sheet(excel_data, ['移动配置','Movement Config','movement'])),
  69. 'visual': self._parse_visual_config(self._get_sheet(excel_data, ['视觉配置','Visual Config','visual'])),
  70. 'audio': self._parse_audio_config(self._get_sheet(excel_data, ['音频配置','Audio Config','audio'])),
  71. 'special': self._parse_special_abilities(self._get_sheet(excel_data, ['特殊能力配置','Special Ability Config','special','abilities','特殊能力'])),
  72. 'boss': self._parse_boss_config(self._get_sheet(excel_data, ['BOSS配置','Boss Config','boss']))
  73. }
  74. print("Excel数据解析完成")
  75. return True
  76. except Exception as e:
  77. print(f"读取Excel文件失败: {e}")
  78. return False
  79. def _parse_basic_config(self, df):
  80. """解析敌人基础配置"""
  81. if df is None:
  82. return {}
  83. config = {}
  84. # 从第1行开始处理(跳过第0行标题)
  85. for i in range(1, len(df)):
  86. row = df.iloc[i]
  87. enemy_id = str(row.iloc[0]).strip()
  88. if pd.isna(row.iloc[0]) or enemy_id == '':
  89. continue
  90. config[enemy_id] = {
  91. 'name': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '',
  92. 'type': str(row.iloc[2]) if not pd.isna(row.iloc[2]) else '',
  93. 'health': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 100,
  94. 'maxHealth': int(row.iloc[4]) if not pd.isna(row.iloc[4]) else 100,
  95. 'defense': int(row.iloc[5]) if not pd.isna(row.iloc[5]) else 0,
  96. 'speed': float(row.iloc[6]) if not pd.isna(row.iloc[6]) else 30.0,
  97. 'dropEnergy': int(row.iloc[7]) if not pd.isna(row.iloc[7]) else 1,
  98. 'dropCoins': int(row.iloc[8]) if not pd.isna(row.iloc[8]) else 5
  99. }
  100. print(f"解析基础配置: {len(config)} 个敌人")
  101. return config
  102. def _parse_combat_config(self, df):
  103. """解析战斗配置"""
  104. if df is None:
  105. return {}
  106. config = {}
  107. for index, row in df.iterrows():
  108. if index == 0: # 跳过标题行
  109. continue
  110. enemy_id = str(row.iloc[0]).strip()
  111. if pd.isna(row.iloc[0]) or enemy_id == '':
  112. continue
  113. config[enemy_id] = {
  114. 'attackDamage': int(row.iloc[1]) if not pd.isna(row.iloc[1]) else 1,
  115. 'attackRange': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1.0,
  116. 'attackSpeed': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 1.0,
  117. 'canBlock': self._parse_boolean(row.iloc[4], False),
  118. 'blockChance': float(row.iloc[5]) if not pd.isna(row.iloc[5]) else 0.0,
  119. 'blockDamageReduction': float(row.iloc[6]) if not pd.isna(row.iloc[6]) else 0.5,
  120. 'attackCooldown': float(row.iloc[7]) if not pd.isna(row.iloc[7]) else 1.0,
  121. 'attackType': str(row.iloc[8]) if not pd.isna(row.iloc[8]) else 'melee',
  122. # 第9列是"备注"字段,跳过不导入
  123. 'causesWallShake': self._parse_boolean(row.iloc[10], False),
  124. 'attackDelay': float(row.iloc[11]) if not pd.isna(row.iloc[11]) else 1.0,
  125. 'weaponType': str(row.iloc[12]) if not pd.isna(row.iloc[12]) else 'none',
  126. 'projectileType': str(row.iloc[13]) if not pd.isna(row.iloc[13]) else 'none',
  127. 'projectileSpeed': float(row.iloc[14]) if not pd.isna(row.iloc[14]) else 100.0
  128. }
  129. print(f"解析战斗配置: {len(config)} 个敌人")
  130. return config
  131. def _parse_movement_config(self, df):
  132. """解析移动配置"""
  133. if df is None:
  134. return {}
  135. config = {}
  136. for index, row in df.iterrows():
  137. if index == 0: # 跳过标题行
  138. continue
  139. enemy_id = str(row.iloc[0]).strip()
  140. if pd.isna(row.iloc[0]) or enemy_id == '':
  141. continue
  142. config[enemy_id] = {
  143. 'pattern': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else 'direct',
  144. 'speed': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 30.0,
  145. 'patrolRange': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 100,
  146. 'rotationSpeed': float(row.iloc[4]) if not pd.isna(row.iloc[4]) else 180.0,
  147. 'moveType': str(row.iloc[5]) if not pd.isna(row.iloc[5]) else 'straight',
  148. 'swingAmplitude': float(row.iloc[6]) if not pd.isna(row.iloc[6]) else 0.0,
  149. 'swingFrequency': float(row.iloc[7]) if not pd.isna(row.iloc[7]) else 0.0,
  150. 'speedVariation': float(row.iloc[8]) if not pd.isna(row.iloc[8]) else 0.1
  151. }
  152. print(f"解析移动配置: {len(config)} 个敌人")
  153. return config
  154. def _parse_visual_config(self, df):
  155. """解析视觉配置"""
  156. if df is None:
  157. return {}
  158. visual_config = {}
  159. for index, row in df.iterrows():
  160. if index == 0: # 跳过标题行
  161. continue
  162. enemy_id = str(row.iloc[0]).strip()
  163. if pd.isna(row.iloc[0]) or enemy_id == '':
  164. continue
  165. config = {
  166. 'sprite_path': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '',
  167. 'scale': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1.0,
  168. 'animation_speed': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 1.0,
  169. 'flip_horizontal': self._parse_boolean(row.iloc[4], False),
  170. 'animations': {
  171. 'idle': str(row.iloc[5]) if not pd.isna(row.iloc[5]) else 'idle',
  172. 'walk': str(row.iloc[6]) if not pd.isna(row.iloc[6]) else 'walk',
  173. 'attack': str(row.iloc[7]) if not pd.isna(row.iloc[7]) else 'attack',
  174. 'death': str(row.iloc[8]) if not pd.isna(row.iloc[8]) else 'dead'
  175. }
  176. }
  177. # 武器道具(可选)
  178. if len(row) > 9 and not pd.isna(row.iloc[9]):
  179. weapon_prop = str(row.iloc[9]).strip()
  180. if weapon_prop:
  181. config['weapon_prop'] = weapon_prop
  182. # 色调(可选),第10列
  183. if len(row) > 10 and not pd.isna(row.iloc[10]):
  184. tint_val = str(row.iloc[10]).strip()
  185. if tint_val:
  186. config['tint'] = tint_val
  187. visual_config[enemy_id] = config
  188. print(f"解析视觉配置: {len(visual_config)} 个敌人")
  189. return visual_config
  190. def _parse_audio_config(self, df):
  191. """解析音频配置"""
  192. if df is None:
  193. return {}
  194. audio_config = {}
  195. for index, row in df.iterrows():
  196. if index == 0: # 跳过标题行
  197. continue
  198. enemy_id = str(row.iloc[0]).strip()
  199. if pd.isna(row.iloc[0]) or enemy_id == '':
  200. continue
  201. config = {
  202. 'attack_sound': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '',
  203. 'death_sound': str(row.iloc[2]) if not pd.isna(row.iloc[2]) else '',
  204. 'hit_sound': str(row.iloc[3]) if not pd.isna(row.iloc[3]) else '',
  205. 'walk_sound': str(row.iloc[4]) if not pd.isna(row.iloc[4]) else ''
  206. }
  207. audio_config[enemy_id] = config
  208. print(f"解析音频配置: {len(audio_config)} 个敌人")
  209. return audio_config
  210. def _parse_special_abilities(self, df):
  211. """解析特殊能力配置"""
  212. if df is None:
  213. return {}
  214. abilities_config = {}
  215. for index, row in df.iterrows():
  216. if index == 0: # 跳过标题行
  217. continue
  218. enemy_id = str(row.iloc[0]).strip()
  219. if pd.isna(row.iloc[0]) or enemy_id == '':
  220. continue
  221. ability_type = str(row.iloc[1]).strip() if not pd.isna(row.iloc[1]) else ''
  222. if not ability_type:
  223. # 没有特殊能力的敌人
  224. if enemy_id not in abilities_config:
  225. abilities_config[enemy_id] = []
  226. continue
  227. ability = {
  228. 'type': ability_type,
  229. 'damage': int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 0,
  230. 'range': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 0,
  231. 'cooldown': int(row.iloc[4]) if not pd.isna(row.iloc[4]) else 0
  232. }
  233. if enemy_id not in abilities_config:
  234. abilities_config[enemy_id] = []
  235. abilities_config[enemy_id].append(ability)
  236. print(f"解析特殊能力配置: {len(abilities_config)} 个敌人")
  237. return abilities_config
  238. def _parse_boss_config(self, df):
  239. """解析BOSS配置"""
  240. if df is None:
  241. return {}
  242. boss_config = {}
  243. for index, row in df.iterrows():
  244. if index == 0: # 跳过标题行
  245. continue
  246. enemy_id = str(row.iloc[0]).strip()
  247. if pd.isna(row.iloc[0]) or enemy_id == '':
  248. continue
  249. config = {
  250. 'is_boss': self._parse_boolean(row.iloc[1], False),
  251. 'phases': int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1,
  252. 'rage_threshold': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 0.3,
  253. 'rage_damage_multiplier': float(row.iloc[4]) if not pd.isna(row.iloc[4]) else 1.0,
  254. 'rage_speed_multiplier': float(row.iloc[5]) if not pd.isna(row.iloc[5]) else 1.0
  255. }
  256. boss_config[enemy_id] = config
  257. print(f"解析BOSS配置: {len(boss_config)} 个敌人")
  258. return boss_config
  259. # 新增:布尔解析和工作表匹配助手
  260. def _parse_boolean(self, value, default=False):
  261. try:
  262. if value is None or (PANDAS_AVAILABLE and pd.isna(value)):
  263. return default
  264. if isinstance(value, bool):
  265. return value
  266. try:
  267. import numpy as np
  268. if isinstance(value, (np.bool_,)):
  269. return bool(value)
  270. except Exception:
  271. pass
  272. if isinstance(value, (int, float)):
  273. return int(value) != 0
  274. s = str(value).strip()
  275. if s == '':
  276. return default
  277. sl = s.lower() if CASE_INSENSITIVE else s
  278. true_set = {'true','t','yes','y','on','1','是','对','开','开启','启用'}
  279. false_set = {'false','f','no','n','off','0','否','错','关','关闭','禁用'}
  280. if (CASE_INSENSITIVE and sl in true_set) or (not CASE_INSENSITIVE and s in true_set):
  281. return True
  282. if (CASE_INSENSITIVE and sl in false_set) or (not CASE_INSENSITIVE and s in false_set):
  283. return False
  284. try:
  285. return float(s) != 0.0
  286. except Exception:
  287. return default
  288. except Exception:
  289. return default
  290. def _get_sheet(self, all_sheets, candidates):
  291. if all_sheets is None:
  292. return None
  293. if CASE_INSENSITIVE:
  294. key_map = {str(k).strip().lower(): k for k in all_sheets.keys()}
  295. for name in candidates:
  296. key = str(name).strip().lower()
  297. if key in key_map:
  298. return all_sheets[key_map[key]]
  299. return None
  300. else:
  301. for name in candidates:
  302. if name in all_sheets:
  303. return all_sheets[name]
  304. return None
  305. def merge_configurations(self):
  306. """合并Excel配置和现有JSON配置"""
  307. print("开始合并配置...")
  308. # 创建敌人ID到JSON索引的映射
  309. json_enemy_map = {}
  310. for i, enemy in enumerate(self.existing_json):
  311. json_enemy_map[enemy.get('id', '')] = i
  312. # 获取所有Excel中的敌人ID
  313. excel_enemy_ids = set()
  314. for config_type in ['basic', 'combat', 'movement', 'visual', 'audio', 'special', 'boss']:
  315. if config_type in self.excel_data:
  316. excel_enemy_ids.update(self.excel_data[config_type].keys())
  317. # 复制现有JSON作为基础
  318. self.merged_config = copy.deepcopy(self.existing_json)
  319. # 处理每个敌人
  320. for enemy_id in excel_enemy_ids:
  321. if enemy_id in json_enemy_map:
  322. # 更新现有敌人
  323. json_index = json_enemy_map[enemy_id]
  324. self._update_enemy_config(self.merged_config[json_index], enemy_id)
  325. print(f"更新敌人配置: {enemy_id}")
  326. else:
  327. # 创建新敌人(如果需要)
  328. new_enemy = self._create_new_enemy_config(enemy_id)
  329. if new_enemy:
  330. self.merged_config.append(new_enemy)
  331. print(f"创建新敌人配置: {enemy_id}")
  332. print(f"配置合并完成,共 {len(self.merged_config)} 个敌人")
  333. def _update_enemy_config(self, enemy_config, enemy_id):
  334. """更新单个敌人的配置"""
  335. # 更新基础配置
  336. if 'basic' in self.excel_data and enemy_id in self.excel_data['basic']:
  337. basic_data = self.excel_data['basic'][enemy_id]
  338. if 'name' in basic_data:
  339. enemy_config['name'] = basic_data['name']
  340. if 'type' in basic_data:
  341. enemy_config['type'] = basic_data['type']
  342. # 更新stats部分
  343. if 'stats' not in enemy_config:
  344. enemy_config['stats'] = {}
  345. stats_mapping = {
  346. 'health': 'health',
  347. 'maxHealth': 'maxHealth',
  348. 'defense': 'defense',
  349. 'speed': 'speed',
  350. 'dropEnergy': 'dropEnergy',
  351. 'dropCoins': 'dropCoins'
  352. }
  353. for excel_key, json_key in stats_mapping.items():
  354. if excel_key in basic_data:
  355. enemy_config['stats'][json_key] = basic_data[excel_key]
  356. # 更新战斗配置
  357. if 'combat' in self.excel_data and enemy_id in self.excel_data['combat']:
  358. combat_data = self.excel_data['combat'][enemy_id]
  359. if 'combat' not in enemy_config:
  360. enemy_config['combat'] = {}
  361. combat_mapping = {
  362. 'attackDamage': 'attackDamage',
  363. 'attackRange': 'attackRange',
  364. 'attackSpeed': 'attackSpeed',
  365. 'canBlock': 'canBlock',
  366. 'blockChance': 'blockChance',
  367. 'blockDamageReduction': 'blockDamageReduction',
  368. 'attackCooldown': 'attackCooldown',
  369. 'attackType': 'attackType',
  370. 'causesWallShake': 'causesWallShake',
  371. 'attackDelay': 'attackDelay',
  372. 'weaponType': 'weaponType',
  373. 'projectileType': 'projectileType',
  374. 'projectileSpeed': 'projectileSpeed'
  375. }
  376. for excel_key, json_key in combat_mapping.items():
  377. if excel_key in combat_data:
  378. enemy_config['combat'][json_key] = combat_data[excel_key]
  379. # 更新移动配置
  380. if 'movement' in self.excel_data and enemy_id in self.excel_data['movement']:
  381. movement_data = self.excel_data['movement'][enemy_id]
  382. if 'movement' not in enemy_config:
  383. enemy_config['movement'] = {}
  384. movement_mapping = {
  385. 'pattern': 'pattern',
  386. 'speed': 'speed',
  387. 'patrolRange': 'patrolRange',
  388. 'rotationSpeed': 'rotationSpeed',
  389. 'moveType': 'moveType',
  390. 'swingAmplitude': 'swingAmplitude',
  391. 'swingFrequency': 'swingFrequency',
  392. 'speedVariation': 'speedVariation'
  393. }
  394. for excel_key, json_key in movement_mapping.items():
  395. if excel_key in movement_data:
  396. enemy_config['movement'][json_key] = movement_data[excel_key]
  397. # 更新视觉配置
  398. if 'visual' in self.excel_data and enemy_id in self.excel_data['visual']:
  399. visual_data = self.excel_data['visual'][enemy_id]
  400. if 'visual' not in enemy_config:
  401. enemy_config['visual'] = {}
  402. # 直接映射视觉配置
  403. for key, value in visual_data.items():
  404. enemy_config['visual'][key] = value
  405. # 同步到 visualConfig(供引擎使用的驼峰命名结构)
  406. if 'visualConfig' not in enemy_config:
  407. enemy_config['visualConfig'] = {}
  408. vc = enemy_config['visualConfig']
  409. v = enemy_config['visual']
  410. # 基础字段映射:下划线 -> 驼峰
  411. vc['spritePath'] = v.get('sprite_path', vc.get('spritePath', ''))
  412. vc['scale'] = v.get('scale', vc.get('scale', 1.0))
  413. vc['animationSpeed'] = v.get('animation_speed', vc.get('animationSpeed', 1.0))
  414. vc['flipX'] = v.get('flip_horizontal', vc.get('flipX', False))
  415. # animations 映射,保留已有值作为默认
  416. v_anims = v.get('animations', {})
  417. if 'animations' not in vc:
  418. vc['animations'] = {}
  419. for key in ['idle', 'walk', 'attack', 'death']:
  420. if key in v_anims:
  421. vc['animations'][key] = v_anims[key]
  422. else:
  423. vc['animations'].setdefault(key, vc['animations'].get(key, key))
  424. # weaponProp 映射(可选)
  425. if 'weapon_prop' in v:
  426. vc['weaponProp'] = v['weapon_prop']
  427. # tint 保留已有 JSON 值,若无则设为默认白色
  428. if 'tint' in v:
  429. vc['tint'] = v['tint']
  430. else:
  431. vc['tint'] = vc.get('tint', '#FFFFFF')
  432. # 更新音频配置
  433. if 'audio' in self.excel_data and enemy_id in self.excel_data['audio']:
  434. audio_data = self.excel_data['audio'][enemy_id]
  435. if 'audio' not in enemy_config:
  436. enemy_config['audio'] = {}
  437. # 直接映射音频配置
  438. for key, value in audio_data.items():
  439. enemy_config['audio'][key] = value
  440. # 更新特殊能力配置
  441. if 'special' in self.excel_data and enemy_id in self.excel_data['special']:
  442. abilities_data = self.excel_data['special'][enemy_id]
  443. if 'special_abilities' not in enemy_config:
  444. enemy_config['special_abilities'] = []
  445. # 替换特殊能力列表
  446. enemy_config['special_abilities'] = abilities_data
  447. # 更新BOSS配置
  448. if 'boss' in self.excel_data and enemy_id in self.excel_data['boss']:
  449. boss_data = self.excel_data['boss'][enemy_id]
  450. if 'boss' not in enemy_config:
  451. enemy_config['boss'] = {}
  452. # 直接映射BOSS配置
  453. for key, value in boss_data.items():
  454. enemy_config['boss'][key] = value
  455. def _create_new_enemy_config(self, enemy_id):
  456. """创建新的敌人配置(基于Excel数据)"""
  457. # 这里可以根据需要实现创建新敌人的逻辑
  458. # 暂时返回None,只更新现有敌人
  459. return None
  460. def backup_json(self):
  461. """备份原始JSON文件"""
  462. if not self.json_path.exists():
  463. return True
  464. try:
  465. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  466. backup_path = self.json_path.parent / f"{self.json_path.stem}_backup_{timestamp}.json"
  467. with open(self.json_path, 'r', encoding='utf-8') as src:
  468. with open(backup_path, 'w', encoding='utf-8') as dst:
  469. dst.write(src.read())
  470. print(f"备份文件已创建: {backup_path}")
  471. return True
  472. except Exception as e:
  473. print(f"备份失败: {e}")
  474. return False
  475. def save_merged_config(self):
  476. """保存合并后的配置到JSON文件"""
  477. try:
  478. # 确保目录存在
  479. self.json_path.parent.mkdir(parents=True, exist_ok=True)
  480. with open(self.json_path, 'w', encoding='utf-8') as f:
  481. json.dump(self.merged_config, f, ensure_ascii=False, indent=2)
  482. print(f"配置已保存到: {self.json_path}")
  483. return True
  484. except Exception as e:
  485. print(f"保存配置失败: {e}")
  486. return False
  487. def import_config(self):
  488. """执行完整的配置导入流程"""
  489. print("=== 敌人配置导入开始 ===")
  490. try:
  491. # 1. 加载现有JSON配置
  492. if not self.load_existing_json():
  493. print("加载现有JSON配置失败")
  494. return False
  495. # 2. 读取Excel数据
  496. if not self.read_excel_data():
  497. print("读取Excel数据失败")
  498. return False
  499. # 3. 合并配置
  500. self.merge_configurations()
  501. # 4. 备份原文件
  502. if not self.backup_json():
  503. print("备份失败,但继续执行")
  504. # 5. 保存新配置
  505. if not self.save_merged_config():
  506. print("保存配置失败")
  507. return False
  508. print("=== 敌人配置导入完成 ===")
  509. return True
  510. except Exception as e:
  511. print(f"配置导入过程中发生错误: {e}")
  512. return False
  513. # 测试函数
  514. def test_enemy_config_manager():
  515. """测试敌人配置管理器"""
  516. try:
  517. manager = EnemyConfigManager()
  518. success = manager.import_config()
  519. if success:
  520. print("敌人配置导入测试成功")
  521. else:
  522. print("敌人配置导入测试失败")
  523. return success
  524. except Exception as e:
  525. print(f"测试失败: {e}")
  526. return False
  527. if __name__ == "__main__":
  528. test_enemy_config_manager()